diff options
Diffstat (limited to 'Lib/packaging/tests')
121 files changed, 10551 insertions, 0 deletions
diff --git a/Lib/packaging/tests/LONG_DESC.txt b/Lib/packaging/tests/LONG_DESC.txt new file mode 100644 index 0000000..2b4358a --- /dev/null +++ b/Lib/packaging/tests/LONG_DESC.txt @@ -0,0 +1,44 @@ +CLVault +======= + +CLVault uses Keyring to provide a command-line utility to safely store +and retrieve passwords. + +Install it using pip or the setup.py script:: + + $ python setup.py install + + $ pip install clvault + +Once it's installed, you will have three scripts installed in your +Python scripts folder, you can use to list, store and retrieve passwords:: + + $ clvault-set blog + Set your password: + Set the associated username (can be blank): tarek + Set a description (can be blank): My blog password + Password set. + + $ clvault-get blog + The username is "tarek" + The password has been copied in your clipboard + + $ clvault-list + Registered services: + blog My blog password + + +*clvault-set* takes a service name then prompt you for a password, and some +optional information about your service. The password is safely stored in +a keyring while the description is saved in a ``.clvault`` file in your +home directory. This file is created automatically the first time the command +is used. + +*clvault-get* copies the password for a given service in your clipboard, and +displays the associated user if any. + +*clvault-list* lists all registered services, with their description when +given. + + +Project page: http://bitbucket.org/tarek/clvault diff --git a/Lib/packaging/tests/PKG-INFO b/Lib/packaging/tests/PKG-INFO new file mode 100644 index 0000000..f48546e --- /dev/null +++ b/Lib/packaging/tests/PKG-INFO @@ -0,0 +1,57 @@ +Metadata-Version: 1.2 +Name: CLVault +Version: 0.5 +Summary: Command-Line utility to store and retrieve passwords +Home-page: http://bitbucket.org/tarek/clvault +Author: Tarek Ziade +Author-email: tarek@ziade.org +License: PSF +Keywords: keyring,password,crypt +Requires-Dist: foo; sys.platform == 'okook' +Requires-Dist: bar; sys.platform == '%s' +Platform: UNKNOWN +Description: CLVault + |======= + | + |CLVault uses Keyring to provide a command-line utility to safely store + |and retrieve passwords. + | + |Install it using pip or the setup.py script:: + | + | $ python setup.py install + | + | $ pip install clvault + | + |Once it's installed, you will have three scripts installed in your + |Python scripts folder, you can use to list, store and retrieve passwords:: + | + | $ clvault-set blog + | Set your password: + | Set the associated username (can be blank): tarek + | Set a description (can be blank): My blog password + | Password set. + | + | $ clvault-get blog + | The username is "tarek" + | The password has been copied in your clipboard + | + | $ clvault-list + | Registered services: + | blog My blog password + | + | + |*clvault-set* takes a service name then prompt you for a password, and some + |optional information about your service. The password is safely stored in + |a keyring while the description is saved in a ``.clvault`` file in your + |home directory. This file is created automatically the first time the command + |is used. + | + |*clvault-get* copies the password for a given service in your clipboard, and + |displays the associated user if any. + | + |*clvault-list* lists all registered services, with their description when + |given. + | + | + |Project page: http://bitbucket.org/tarek/clvault + | diff --git a/Lib/packaging/tests/SETUPTOOLS-PKG-INFO b/Lib/packaging/tests/SETUPTOOLS-PKG-INFO new file mode 100644 index 0000000..dff8d00 --- /dev/null +++ b/Lib/packaging/tests/SETUPTOOLS-PKG-INFO @@ -0,0 +1,182 @@ +Metadata-Version: 1.0 +Name: setuptools +Version: 0.6c9 +Summary: Download, build, install, upgrade, and uninstall Python packages -- easily! +Home-page: http://pypi.python.org/pypi/setuptools +Author: Phillip J. Eby +Author-email: distutils-sig@python.org +License: PSF or ZPL +Description: =============================== + Installing and Using Setuptools + =============================== + + .. contents:: **Table of Contents** + + + ------------------------- + Installation Instructions + ------------------------- + + Windows + ======= + + Install setuptools using the provided ``.exe`` installer. If you've previously + installed older versions of setuptools, please delete all ``setuptools*.egg`` + and ``setuptools.pth`` files from your system's ``site-packages`` directory + (and any other ``sys.path`` directories) FIRST. + + If you are upgrading a previous version of setuptools that was installed using + an ``.exe`` installer, please be sure to also *uninstall that older version* + via your system's "Add/Remove Programs" feature, BEFORE installing the newer + version. + + Once installation is complete, you will find an ``easy_install.exe`` program in + your Python ``Scripts`` subdirectory. Be sure to add this directory to your + ``PATH`` environment variable, if you haven't already done so. + + + RPM-Based Systems + ================= + + Install setuptools using the provided source RPM. The included ``.spec`` file + assumes you are installing using the default ``python`` executable, and is not + specific to a particular Python version. The ``easy_install`` executable will + be installed to a system ``bin`` directory such as ``/usr/bin``. + + If you wish to install to a location other than the default Python + installation's default ``site-packages`` directory (and ``$prefix/bin`` for + scripts), please use the ``.egg``-based installation approach described in the + following section. + + + Cygwin, Mac OS X, Linux, Other + ============================== + + 1. Download the appropriate egg for your version of Python (e.g. + ``setuptools-0.6c9-py2.4.egg``). Do NOT rename it. + + 2. Run it as if it were a shell script, e.g. ``sh setuptools-0.6c9-py2.4.egg``. + Setuptools will install itself using the matching version of Python (e.g. + ``python2.4``), and will place the ``easy_install`` executable in the + default location for installing Python scripts (as determined by the + standard distutils configuration files, or by the Python installation). + + If you want to install setuptools to somewhere other than ``site-packages`` or + your default distutils installation locations for libraries and scripts, you + may include EasyInstall command-line options such as ``--prefix``, + ``--install-dir``, and so on, following the ``.egg`` filename on the same + command line. For example:: + + sh setuptools-0.6c9-py2.4.egg --prefix=~ + + You can use ``--help`` to get a full options list, but we recommend consulting + the `EasyInstall manual`_ for detailed instructions, especially `the section + on custom installation locations`_. + + .. _EasyInstall manual: http://peak.telecommunity.com/DevCenter/EasyInstall + .. _the section on custom installation locations: http://peak.telecommunity.com/DevCenter/EasyInstall#custom-installation-locations + + + Cygwin Note + ----------- + + If you are trying to install setuptools for the **Windows** version of Python + (as opposed to the Cygwin version that lives in ``/usr/bin``), you must make + sure that an appropriate executable (``python2.3``, ``python2.4``, or + ``python2.5``) is on your **Cygwin** ``PATH`` when invoking the egg. For + example, doing the following at a Cygwin bash prompt will install setuptools + for the **Windows** Python found at ``C:\\Python24``:: + + ln -s /cygdrive/c/Python24/python.exe python2.4 + PATH=.:$PATH sh setuptools-0.6c9-py2.4.egg + rm python2.4 + + + Downloads + ========= + + All setuptools downloads can be found at `the project's home page in the Python + Package Index`_. Scroll to the very bottom of the page to find the links. + + .. _the project's home page in the Python Package Index: http://pypi.python.org/pypi/setuptools + + In addition to the PyPI downloads, the development version of ``setuptools`` + is available from the `Python SVN sandbox`_, and in-development versions of the + `0.6 branch`_ are available as well. + + .. _0.6 branch: http://svn.python.org/projects/sandbox/branches/setuptools-0.6/#egg=setuptools-dev06 + + .. _Python SVN sandbox: http://svn.python.org/projects/sandbox/trunk/setuptools/#egg=setuptools-dev + + -------------------------------- + Using Setuptools and EasyInstall + -------------------------------- + + Here are some of the available manuals, tutorials, and other resources for + learning about Setuptools, Python Eggs, and EasyInstall: + + * `The EasyInstall user's guide and reference manual`_ + * `The setuptools Developer's Guide`_ + * `The pkg_resources API reference`_ + * `Package Compatibility Notes`_ (user-maintained) + * `The Internal Structure of Python Eggs`_ + + Questions, comments, and bug reports should be directed to the `distutils-sig + mailing list`_. If you have written (or know of) any tutorials, documentation, + plug-ins, or other resources for setuptools users, please let us know about + them there, so this reference list can be updated. If you have working, + *tested* patches to correct problems or add features, you may submit them to + the `setuptools bug tracker`_. + + .. _setuptools bug tracker: http://bugs.python.org/setuptools/ + .. _Package Compatibility Notes: http://peak.telecommunity.com/DevCenter/PackageNotes + .. _The Internal Structure of Python Eggs: http://peak.telecommunity.com/DevCenter/EggFormats + .. _The setuptools Developer's Guide: http://peak.telecommunity.com/DevCenter/setuptools + .. _The pkg_resources API reference: http://peak.telecommunity.com/DevCenter/PkgResources + .. _The EasyInstall user's guide and reference manual: http://peak.telecommunity.com/DevCenter/EasyInstall + .. _distutils-sig mailing list: http://mail.python.org/pipermail/distutils-sig/ + + + ------- + Credits + ------- + + * The original design for the ``.egg`` format and the ``pkg_resources`` API was + co-created by Phillip Eby and Bob Ippolito. Bob also implemented the first + version of ``pkg_resources``, and supplied the OS X operating system version + compatibility algorithm. + + * Ian Bicking implemented many early "creature comfort" features of + easy_install, including support for downloading via Sourceforge and + Subversion repositories. Ian's comments on the Web-SIG about WSGI + application deployment also inspired the concept of "entry points" in eggs, + and he has given talks at PyCon and elsewhere to inform and educate the + community about eggs and setuptools. + + * Jim Fulton contributed time and effort to build automated tests of various + aspects of ``easy_install``, and supplied the doctests for the command-line + ``.exe`` wrappers on Windows. + + * Phillip J. Eby is the principal author and maintainer of setuptools, and + first proposed the idea of an importable binary distribution format for + Python application plug-ins. + + * Significant parts of the implementation of setuptools were funded by the Open + Source Applications Foundation, to provide a plug-in infrastructure for the + Chandler PIM application. In addition, many OSAF staffers (such as Mike + "Code Bear" Taylor) contributed their time and stress as guinea pigs for the + use of eggs and setuptools, even before eggs were "cool". (Thanks, guys!) + + +Keywords: CPAN PyPI distutils eggs package management +Platform: UNKNOWN +Classifier: Development Status :: 3 - Alpha +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Python Software Foundation License +Classifier: License :: OSI Approved :: Zope Public License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: System :: Archiving :: Packaging +Classifier: Topic :: System :: Systems Administration +Classifier: Topic :: Utilities diff --git a/Lib/packaging/tests/SETUPTOOLS-PKG-INFO2 b/Lib/packaging/tests/SETUPTOOLS-PKG-INFO2 new file mode 100644 index 0000000..4b3906a --- /dev/null +++ b/Lib/packaging/tests/SETUPTOOLS-PKG-INFO2 @@ -0,0 +1,183 @@ +Metadata-Version: 1.1 +Name: setuptools +Version: 0.6c9 +Summary: Download, build, install, upgrade, and uninstall Python packages -- easily! +Home-page: http://pypi.python.org/pypi/setuptools +Author: Phillip J. Eby +Author-email: distutils-sig@python.org +License: PSF or ZPL +Description: =============================== + Installing and Using Setuptools + =============================== + + .. contents:: **Table of Contents** + + + ------------------------- + Installation Instructions + ------------------------- + + Windows + ======= + + Install setuptools using the provided ``.exe`` installer. If you've previously + installed older versions of setuptools, please delete all ``setuptools*.egg`` + and ``setuptools.pth`` files from your system's ``site-packages`` directory + (and any other ``sys.path`` directories) FIRST. + + If you are upgrading a previous version of setuptools that was installed using + an ``.exe`` installer, please be sure to also *uninstall that older version* + via your system's "Add/Remove Programs" feature, BEFORE installing the newer + version. + + Once installation is complete, you will find an ``easy_install.exe`` program in + your Python ``Scripts`` subdirectory. Be sure to add this directory to your + ``PATH`` environment variable, if you haven't already done so. + + + RPM-Based Systems + ================= + + Install setuptools using the provided source RPM. The included ``.spec`` file + assumes you are installing using the default ``python`` executable, and is not + specific to a particular Python version. The ``easy_install`` executable will + be installed to a system ``bin`` directory such as ``/usr/bin``. + + If you wish to install to a location other than the default Python + installation's default ``site-packages`` directory (and ``$prefix/bin`` for + scripts), please use the ``.egg``-based installation approach described in the + following section. + + + Cygwin, Mac OS X, Linux, Other + ============================== + + 1. Download the appropriate egg for your version of Python (e.g. + ``setuptools-0.6c9-py2.4.egg``). Do NOT rename it. + + 2. Run it as if it were a shell script, e.g. ``sh setuptools-0.6c9-py2.4.egg``. + Setuptools will install itself using the matching version of Python (e.g. + ``python2.4``), and will place the ``easy_install`` executable in the + default location for installing Python scripts (as determined by the + standard distutils configuration files, or by the Python installation). + + If you want to install setuptools to somewhere other than ``site-packages`` or + your default distutils installation locations for libraries and scripts, you + may include EasyInstall command-line options such as ``--prefix``, + ``--install-dir``, and so on, following the ``.egg`` filename on the same + command line. For example:: + + sh setuptools-0.6c9-py2.4.egg --prefix=~ + + You can use ``--help`` to get a full options list, but we recommend consulting + the `EasyInstall manual`_ for detailed instructions, especially `the section + on custom installation locations`_. + + .. _EasyInstall manual: http://peak.telecommunity.com/DevCenter/EasyInstall + .. _the section on custom installation locations: http://peak.telecommunity.com/DevCenter/EasyInstall#custom-installation-locations + + + Cygwin Note + ----------- + + If you are trying to install setuptools for the **Windows** version of Python + (as opposed to the Cygwin version that lives in ``/usr/bin``), you must make + sure that an appropriate executable (``python2.3``, ``python2.4``, or + ``python2.5``) is on your **Cygwin** ``PATH`` when invoking the egg. For + example, doing the following at a Cygwin bash prompt will install setuptools + for the **Windows** Python found at ``C:\\Python24``:: + + ln -s /cygdrive/c/Python24/python.exe python2.4 + PATH=.:$PATH sh setuptools-0.6c9-py2.4.egg + rm python2.4 + + + Downloads + ========= + + All setuptools downloads can be found at `the project's home page in the Python + Package Index`_. Scroll to the very bottom of the page to find the links. + + .. _the project's home page in the Python Package Index: http://pypi.python.org/pypi/setuptools + + In addition to the PyPI downloads, the development version of ``setuptools`` + is available from the `Python SVN sandbox`_, and in-development versions of the + `0.6 branch`_ are available as well. + + .. _0.6 branch: http://svn.python.org/projects/sandbox/branches/setuptools-0.6/#egg=setuptools-dev06 + + .. _Python SVN sandbox: http://svn.python.org/projects/sandbox/trunk/setuptools/#egg=setuptools-dev + + -------------------------------- + Using Setuptools and EasyInstall + -------------------------------- + + Here are some of the available manuals, tutorials, and other resources for + learning about Setuptools, Python Eggs, and EasyInstall: + + * `The EasyInstall user's guide and reference manual`_ + * `The setuptools Developer's Guide`_ + * `The pkg_resources API reference`_ + * `Package Compatibility Notes`_ (user-maintained) + * `The Internal Structure of Python Eggs`_ + + Questions, comments, and bug reports should be directed to the `distutils-sig + mailing list`_. If you have written (or know of) any tutorials, documentation, + plug-ins, or other resources for setuptools users, please let us know about + them there, so this reference list can be updated. If you have working, + *tested* patches to correct problems or add features, you may submit them to + the `setuptools bug tracker`_. + + .. _setuptools bug tracker: http://bugs.python.org/setuptools/ + .. _Package Compatibility Notes: http://peak.telecommunity.com/DevCenter/PackageNotes + .. _The Internal Structure of Python Eggs: http://peak.telecommunity.com/DevCenter/EggFormats + .. _The setuptools Developer's Guide: http://peak.telecommunity.com/DevCenter/setuptools + .. _The pkg_resources API reference: http://peak.telecommunity.com/DevCenter/PkgResources + .. _The EasyInstall user's guide and reference manual: http://peak.telecommunity.com/DevCenter/EasyInstall + .. _distutils-sig mailing list: http://mail.python.org/pipermail/distutils-sig/ + + + ------- + Credits + ------- + + * The original design for the ``.egg`` format and the ``pkg_resources`` API was + co-created by Phillip Eby and Bob Ippolito. Bob also implemented the first + version of ``pkg_resources``, and supplied the OS X operating system version + compatibility algorithm. + + * Ian Bicking implemented many early "creature comfort" features of + easy_install, including support for downloading via Sourceforge and + Subversion repositories. Ian's comments on the Web-SIG about WSGI + application deployment also inspired the concept of "entry points" in eggs, + and he has given talks at PyCon and elsewhere to inform and educate the + community about eggs and setuptools. + + * Jim Fulton contributed time and effort to build automated tests of various + aspects of ``easy_install``, and supplied the doctests for the command-line + ``.exe`` wrappers on Windows. + + * Phillip J. Eby is the principal author and maintainer of setuptools, and + first proposed the idea of an importable binary distribution format for + Python application plug-ins. + + * Significant parts of the implementation of setuptools were funded by the Open + Source Applications Foundation, to provide a plug-in infrastructure for the + Chandler PIM application. In addition, many OSAF staffers (such as Mike + "Code Bear" Taylor) contributed their time and stress as guinea pigs for the + use of eggs and setuptools, even before eggs were "cool". (Thanks, guys!) + + +Keywords: CPAN PyPI distutils eggs package management +Platform: UNKNOWN +Classifier: Development Status :: 3 - Alpha +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Python Software Foundation License +Classifier: License :: OSI Approved :: Zope Public License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: System :: Archiving :: Packaging +Classifier: Topic :: System :: Systems Administration +Classifier: Topic :: Utilities +Requires: Foo diff --git a/Lib/packaging/tests/__init__.py b/Lib/packaging/tests/__init__.py new file mode 100644 index 0000000..0b0e3c5 --- /dev/null +++ b/Lib/packaging/tests/__init__.py @@ -0,0 +1,133 @@ +"""Test suite for packaging. + +This test suite consists of a collection of test modules in the +packaging.tests package. Each test module has a name starting with +'test' and contains a function test_suite(). The function is expected +to return an initialized unittest.TestSuite instance. + +Utility code is included in packaging.tests.support. +""" + +# Put this text back for the backport +#Always import unittest from this module, it will be the right version +#(standard library unittest for 3.2 and higher, third-party unittest2 +#elease for older versions). + +import os +import sys +import unittest +from test.support import TESTFN + +# XXX move helpers to support, add tests for them, remove things that +# duplicate test.support (or keep them for the backport; needs thinking) + +here = os.path.dirname(__file__) or os.curdir +verbose = 1 + +def test_suite(): + suite = unittest.TestSuite() + for fn in os.listdir(here): + if fn.startswith("test") and fn.endswith(".py"): + modname = "packaging.tests." + fn[:-3] + __import__(modname) + module = sys.modules[modname] + suite.addTest(module.test_suite()) + return suite + + +class Error(Exception): + """Base class for regression test exceptions.""" + + +class TestFailed(Error): + """Test failed.""" + + +class BasicTestRunner: + def run(self, test): + result = unittest.TestResult() + test(result) + return result + + +def _run_suite(suite, verbose_=1): + """Run tests from a unittest.TestSuite-derived class.""" + global verbose + verbose = verbose_ + if verbose_: + runner = unittest.TextTestRunner(sys.stdout, verbosity=2) + else: + runner = BasicTestRunner() + + result = runner.run(suite) + if not result.wasSuccessful(): + if len(result.errors) == 1 and not result.failures: + err = result.errors[0][1] + elif len(result.failures) == 1 and not result.errors: + err = result.failures[0][1] + else: + err = "errors occurred; run in verbose mode for details" + raise TestFailed(err) + + +def run_unittest(classes, verbose_=1): + """Run tests from unittest.TestCase-derived classes. + + Originally extracted from stdlib test.test_support and modified to + support unittest2. + """ + valid_types = (unittest.TestSuite, unittest.TestCase) + suite = unittest.TestSuite() + for cls in classes: + if isinstance(cls, str): + if cls in sys.modules: + suite.addTest(unittest.findTestCases(sys.modules[cls])) + else: + raise ValueError("str arguments must be keys in sys.modules") + elif isinstance(cls, valid_types): + suite.addTest(cls) + else: + suite.addTest(unittest.makeSuite(cls)) + _run_suite(suite, verbose_) + + +def reap_children(): + """Use this function at the end of test_main() whenever sub-processes + are started. This will help ensure that no extra children (zombies) + stick around to hog resources and create problems when looking + for refleaks. + + Extracted from stdlib test.support. + """ + + # Reap all our dead child processes so we don't leave zombies around. + # These hog resources and might be causing some of the buildbots to die. + if hasattr(os, 'waitpid'): + any_process = -1 + while True: + try: + # This will raise an exception on Windows. That's ok. + pid, status = os.waitpid(any_process, os.WNOHANG) + if pid == 0: + break + except: + break + + +def captured_stdout(func, *args, **kw): + import io + orig_stdout = getattr(sys, 'stdout') + setattr(sys, 'stdout', io.StringIO()) + try: + res = func(*args, **kw) + sys.stdout.seek(0) + return res, sys.stdout.read() + finally: + setattr(sys, 'stdout', orig_stdout) + + +def unload(name): + try: + del sys.modules[name] + except KeyError: + pass diff --git a/Lib/packaging/tests/__main__.py b/Lib/packaging/tests/__main__.py new file mode 100644 index 0000000..68ee229 --- /dev/null +++ b/Lib/packaging/tests/__main__.py @@ -0,0 +1,20 @@ +"""Packaging test suite runner.""" + +# Ripped from importlib tests, thanks Brett! + +import os +import sys +import unittest +from test.support import run_unittest, reap_children + + +def test_main(): + start_dir = os.path.dirname(__file__) + top_dir = os.path.dirname(os.path.dirname(start_dir)) + test_loader = unittest.TestLoader() + run_unittest(test_loader.discover(start_dir, top_level_dir=top_dir)) + reap_children() + + +if __name__ == '__main__': + test_main() diff --git a/Lib/packaging/tests/fake_dists/babar-0.1.dist-info/INSTALLER b/Lib/packaging/tests/fake_dists/babar-0.1.dist-info/INSTALLER new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/babar-0.1.dist-info/INSTALLER diff --git a/Lib/packaging/tests/fake_dists/babar-0.1.dist-info/METADATA b/Lib/packaging/tests/fake_dists/babar-0.1.dist-info/METADATA new file mode 100644 index 0000000..65e839a --- /dev/null +++ b/Lib/packaging/tests/fake_dists/babar-0.1.dist-info/METADATA @@ -0,0 +1,4 @@ +Metadata-version: 1.2 +Name: babar +Version: 0.1 +Author: FELD Boris
\ No newline at end of file diff --git a/Lib/packaging/tests/fake_dists/babar-0.1.dist-info/RECORD b/Lib/packaging/tests/fake_dists/babar-0.1.dist-info/RECORD new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/babar-0.1.dist-info/RECORD diff --git a/Lib/packaging/tests/fake_dists/babar-0.1.dist-info/REQUESTED b/Lib/packaging/tests/fake_dists/babar-0.1.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/babar-0.1.dist-info/REQUESTED diff --git a/Lib/packaging/tests/fake_dists/babar-0.1.dist-info/RESOURCES b/Lib/packaging/tests/fake_dists/babar-0.1.dist-info/RESOURCES new file mode 100644 index 0000000..5d0da49 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/babar-0.1.dist-info/RESOURCES @@ -0,0 +1,2 @@ +babar.png,babar.png +babar.cfg,babar.cfg
\ No newline at end of file diff --git a/Lib/packaging/tests/fake_dists/babar.cfg b/Lib/packaging/tests/fake_dists/babar.cfg new file mode 100644 index 0000000..ecd6efe --- /dev/null +++ b/Lib/packaging/tests/fake_dists/babar.cfg @@ -0,0 +1 @@ +Config
\ No newline at end of file diff --git a/Lib/packaging/tests/fake_dists/babar.png b/Lib/packaging/tests/fake_dists/babar.png new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/babar.png diff --git a/Lib/packaging/tests/fake_dists/bacon-0.1.egg-info/PKG-INFO b/Lib/packaging/tests/fake_dists/bacon-0.1.egg-info/PKG-INFO new file mode 100644 index 0000000..a176dfd --- /dev/null +++ b/Lib/packaging/tests/fake_dists/bacon-0.1.egg-info/PKG-INFO @@ -0,0 +1,6 @@ +Metadata-Version: 1.2 +Name: bacon +Version: 0.1 +Provides-Dist: truffles (2.0) +Provides-Dist: bacon (0.1) +Obsoletes-Dist: truffles (>=0.9,<=1.5) diff --git a/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/PKG-INFO b/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/PKG-INFO new file mode 100644 index 0000000..a7e118a --- /dev/null +++ b/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/PKG-INFO @@ -0,0 +1,18 @@ +Metadata-Version: 1.0 +Name: banana +Version: 0.4 +Summary: A yellow fruit +Home-page: http://en.wikipedia.org/wiki/Banana +Author: Josip Djolonga +Author-email: foo@nbar.com +License: BSD +Description: A fruit +Keywords: foo bar +Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: Science/Research +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Scientific/Engineering :: GIS diff --git a/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/SOURCES.txt b/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/SOURCES.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/SOURCES.txt diff --git a/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/dependency_links.txt b/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/entry_points.txt b/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/entry_points.txt new file mode 100644 index 0000000..5d3e5f6 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/entry_points.txt @@ -0,0 +1,3 @@ + + # -*- Entry points: -*- +
\ No newline at end of file diff --git a/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/not-zip-safe b/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/not-zip-safe new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/not-zip-safe @@ -0,0 +1 @@ + diff --git a/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/requires.txt b/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/requires.txt new file mode 100644 index 0000000..4354305 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/requires.txt @@ -0,0 +1,6 @@ +# this should be ignored + +strawberry >=0.5 + +[section ignored] +foo ==0.5 diff --git a/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/top_level.txt b/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/top_level.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/top_level.txt diff --git a/Lib/packaging/tests/fake_dists/cheese-2.0.2.egg-info b/Lib/packaging/tests/fake_dists/cheese-2.0.2.egg-info new file mode 100644 index 0000000..27cbe30 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/cheese-2.0.2.egg-info @@ -0,0 +1,5 @@ +Metadata-Version: 1.2 +Name: cheese +Version: 2.0.2 +Provides-Dist: truffles (1.0.2) +Obsoletes-Dist: truffles (!=1.2,<=2.0) diff --git a/Lib/packaging/tests/fake_dists/choxie-2.0.0.9.dist-info/INSTALLER b/Lib/packaging/tests/fake_dists/choxie-2.0.0.9.dist-info/INSTALLER new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/choxie-2.0.0.9.dist-info/INSTALLER diff --git a/Lib/packaging/tests/fake_dists/choxie-2.0.0.9.dist-info/METADATA b/Lib/packaging/tests/fake_dists/choxie-2.0.0.9.dist-info/METADATA new file mode 100644 index 0000000..418929e --- /dev/null +++ b/Lib/packaging/tests/fake_dists/choxie-2.0.0.9.dist-info/METADATA @@ -0,0 +1,9 @@ +Metadata-Version: 1.2 +Name: choxie +Version: 2.0.0.9 +Summary: Chocolate with a kick! +Requires-Dist: towel-stuff (0.1) +Requires-Dist: nut +Provides-Dist: truffles (1.0) +Obsoletes-Dist: truffles (<=0.8,>=0.5) +Obsoletes-Dist: truffles (<=0.9,>=0.6) diff --git a/Lib/packaging/tests/fake_dists/choxie-2.0.0.9.dist-info/RECORD b/Lib/packaging/tests/fake_dists/choxie-2.0.0.9.dist-info/RECORD new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/choxie-2.0.0.9.dist-info/RECORD diff --git a/Lib/packaging/tests/fake_dists/choxie-2.0.0.9.dist-info/REQUESTED b/Lib/packaging/tests/fake_dists/choxie-2.0.0.9.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/choxie-2.0.0.9.dist-info/REQUESTED diff --git a/Lib/packaging/tests/fake_dists/choxie-2.0.0.9/choxie/__init__.py b/Lib/packaging/tests/fake_dists/choxie-2.0.0.9/choxie/__init__.py new file mode 100644 index 0000000..40a96af --- /dev/null +++ b/Lib/packaging/tests/fake_dists/choxie-2.0.0.9/choxie/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/Lib/packaging/tests/fake_dists/choxie-2.0.0.9/choxie/chocolate.py b/Lib/packaging/tests/fake_dists/choxie-2.0.0.9/choxie/chocolate.py new file mode 100644 index 0000000..c4027f3 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/choxie-2.0.0.9/choxie/chocolate.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +from towel_stuff import Towel + +class Chocolate(object): + """A piece of chocolate.""" + + def wrap_with_towel(self): + towel = Towel() + towel.wrap(self) + return towel diff --git a/Lib/packaging/tests/fake_dists/choxie-2.0.0.9/truffles.py b/Lib/packaging/tests/fake_dists/choxie-2.0.0.9/truffles.py new file mode 100644 index 0000000..342b8ea --- /dev/null +++ b/Lib/packaging/tests/fake_dists/choxie-2.0.0.9/truffles.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +from choxie.chocolate import Chocolate + +class Truffle(Chocolate): + """A truffle.""" diff --git a/Lib/packaging/tests/fake_dists/coconuts-aster-10.3.egg-info/PKG-INFO b/Lib/packaging/tests/fake_dists/coconuts-aster-10.3.egg-info/PKG-INFO new file mode 100644 index 0000000..499a083 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/coconuts-aster-10.3.egg-info/PKG-INFO @@ -0,0 +1,5 @@ +Metadata-Version: 1.2 +Name: coconuts-aster +Version: 10.3 +Provides-Dist: strawberry (0.6) +Provides-Dist: banana (0.4) diff --git a/Lib/packaging/tests/fake_dists/grammar-1.0a4.dist-info/INSTALLER b/Lib/packaging/tests/fake_dists/grammar-1.0a4.dist-info/INSTALLER new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/grammar-1.0a4.dist-info/INSTALLER diff --git a/Lib/packaging/tests/fake_dists/grammar-1.0a4.dist-info/METADATA b/Lib/packaging/tests/fake_dists/grammar-1.0a4.dist-info/METADATA new file mode 100644 index 0000000..0b99f52 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/grammar-1.0a4.dist-info/METADATA @@ -0,0 +1,5 @@ +Metadata-Version: 1.2 +Name: grammar +Version: 1.0a4 +Requires-Dist: truffles (>=1.2) +Author: Sherlock Holmes diff --git a/Lib/packaging/tests/fake_dists/grammar-1.0a4.dist-info/RECORD b/Lib/packaging/tests/fake_dists/grammar-1.0a4.dist-info/RECORD new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/grammar-1.0a4.dist-info/RECORD diff --git a/Lib/packaging/tests/fake_dists/grammar-1.0a4.dist-info/REQUESTED b/Lib/packaging/tests/fake_dists/grammar-1.0a4.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/grammar-1.0a4.dist-info/REQUESTED diff --git a/Lib/packaging/tests/fake_dists/grammar-1.0a4/grammar/__init__.py b/Lib/packaging/tests/fake_dists/grammar-1.0a4/grammar/__init__.py new file mode 100644 index 0000000..40a96af --- /dev/null +++ b/Lib/packaging/tests/fake_dists/grammar-1.0a4/grammar/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/Lib/packaging/tests/fake_dists/grammar-1.0a4/grammar/utils.py b/Lib/packaging/tests/fake_dists/grammar-1.0a4/grammar/utils.py new file mode 100644 index 0000000..66ba796 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/grammar-1.0a4/grammar/utils.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +from random import randint + +def is_valid_grammar(sentence): + if randint(0, 10) < 2: + return False + else: + return True diff --git a/Lib/packaging/tests/fake_dists/nut-funkyversion.egg-info b/Lib/packaging/tests/fake_dists/nut-funkyversion.egg-info new file mode 100644 index 0000000..0c58ec1 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/nut-funkyversion.egg-info @@ -0,0 +1,3 @@ +Metadata-Version: 1.2 +Name: nut +Version: funkyversion diff --git a/Lib/packaging/tests/fake_dists/strawberry-0.6.egg b/Lib/packaging/tests/fake_dists/strawberry-0.6.egg Binary files differnew file mode 100644 index 0000000..6d160e8 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/strawberry-0.6.egg diff --git a/Lib/packaging/tests/fake_dists/towel_stuff-0.1.dist-info/INSTALLER b/Lib/packaging/tests/fake_dists/towel_stuff-0.1.dist-info/INSTALLER new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/towel_stuff-0.1.dist-info/INSTALLER diff --git a/Lib/packaging/tests/fake_dists/towel_stuff-0.1.dist-info/METADATA b/Lib/packaging/tests/fake_dists/towel_stuff-0.1.dist-info/METADATA new file mode 100644 index 0000000..ca46d0a --- /dev/null +++ b/Lib/packaging/tests/fake_dists/towel_stuff-0.1.dist-info/METADATA @@ -0,0 +1,7 @@ +Metadata-Version: 1.2 +Name: towel-stuff +Version: 0.1 +Provides-Dist: truffles (1.1.2) +Provides-Dist: towel-stuff (0.1) +Obsoletes-Dist: truffles (!=0.8,<1.0) +Requires-Dist: bacon (<=0.2) diff --git a/Lib/packaging/tests/fake_dists/towel_stuff-0.1.dist-info/RECORD b/Lib/packaging/tests/fake_dists/towel_stuff-0.1.dist-info/RECORD new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/towel_stuff-0.1.dist-info/RECORD diff --git a/Lib/packaging/tests/fake_dists/towel_stuff-0.1.dist-info/REQUESTED b/Lib/packaging/tests/fake_dists/towel_stuff-0.1.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/towel_stuff-0.1.dist-info/REQUESTED diff --git a/Lib/packaging/tests/fake_dists/towel_stuff-0.1/towel_stuff/__init__.py b/Lib/packaging/tests/fake_dists/towel_stuff-0.1/towel_stuff/__init__.py new file mode 100644 index 0000000..191f895 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/towel_stuff-0.1/towel_stuff/__init__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +class Towel(object): + """A towel, that one should never be without.""" + + def __init__(self, color='tie-dye'): + self.color = color + self.wrapped_obj = None + + def wrap(self, obj): + """Wrap an object up in our towel.""" + self.wrapped_obj = obj + + def unwrap(self): + """Unwrap whatever is in our towel and return whatever it is.""" + obj = self.wrapped_obj + self.wrapped_obj = None + return obj diff --git a/Lib/packaging/tests/fake_dists/truffles-5.0.egg-info b/Lib/packaging/tests/fake_dists/truffles-5.0.egg-info new file mode 100644 index 0000000..45f0cf8 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/truffles-5.0.egg-info @@ -0,0 +1,3 @@ +Metadata-Version: 1.2 +Name: truffles +Version: 5.0 diff --git a/Lib/packaging/tests/fixer/__init__.py b/Lib/packaging/tests/fixer/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Lib/packaging/tests/fixer/__init__.py diff --git a/Lib/packaging/tests/fixer/fix_idioms.py b/Lib/packaging/tests/fixer/fix_idioms.py new file mode 100644 index 0000000..64f5ea0 --- /dev/null +++ b/Lib/packaging/tests/fixer/fix_idioms.py @@ -0,0 +1,134 @@ +"""Adjust some old Python 2 idioms to their modern counterparts. + +* Change some type comparisons to isinstance() calls: + type(x) == T -> isinstance(x, T) + type(x) is T -> isinstance(x, T) + type(x) != T -> not isinstance(x, T) + type(x) is not T -> not isinstance(x, T) + +* Change "while 1:" into "while True:". + +* Change both + + v = list(EXPR) + v.sort() + foo(v) + +and the more general + + v = EXPR + v.sort() + foo(v) + +into + + v = sorted(EXPR) + foo(v) +""" +# Author: Jacques Frechet, Collin Winter + +# Local imports +from lib2to3 import fixer_base +from lib2to3.fixer_util import Call, Comma, Name, Node, syms + +CMP = "(n='!=' | '==' | 'is' | n=comp_op< 'is' 'not' >)" +TYPE = "power< 'type' trailer< '(' x=any ')' > >" + +class FixIdioms(fixer_base.BaseFix): + + explicit = False # The user must ask for this fixer + + PATTERN = r""" + isinstance=comparison< %s %s T=any > + | + isinstance=comparison< T=any %s %s > + | + while_stmt< 'while' while='1' ':' any+ > + | + sorted=any< + any* + simple_stmt< + expr_stmt< id1=any '=' + power< list='list' trailer< '(' (not arglist<any+>) any ')' > > + > + '\n' + > + sort= + simple_stmt< + power< id2=any + trailer< '.' 'sort' > trailer< '(' ')' > + > + '\n' + > + next=any* + > + | + sorted=any< + any* + simple_stmt< expr_stmt< id1=any '=' expr=any > '\n' > + sort= + simple_stmt< + power< id2=any + trailer< '.' 'sort' > trailer< '(' ')' > + > + '\n' + > + next=any* + > + """ % (TYPE, CMP, CMP, TYPE) + + def match(self, node): + r = super(FixIdioms, self).match(node) + # If we've matched one of the sort/sorted subpatterns above, we + # want to reject matches where the initial assignment and the + # subsequent .sort() call involve different identifiers. + if r and "sorted" in r: + if r["id1"] == r["id2"]: + return r + return None + return r + + def transform(self, node, results): + if "isinstance" in results: + return self.transform_isinstance(node, results) + elif "while" in results: + return self.transform_while(node, results) + elif "sorted" in results: + return self.transform_sort(node, results) + else: + raise RuntimeError("Invalid match") + + def transform_isinstance(self, node, results): + x = results["x"].clone() # The thing inside of type() + T = results["T"].clone() # The type being compared against + x.prefix = "" + T.prefix = " " + test = Call(Name("isinstance"), [x, Comma(), T]) + if "n" in results: + test.prefix = " " + test = Node(syms.not_test, [Name("not"), test]) + test.prefix = node.prefix + return test + + def transform_while(self, node, results): + one = results["while"] + one.replace(Name("True", prefix=one.prefix)) + + def transform_sort(self, node, results): + sort_stmt = results["sort"] + next_stmt = results["next"] + list_call = results.get("list") + simple_expr = results.get("expr") + + if list_call: + list_call.replace(Name("sorted", prefix=list_call.prefix)) + elif simple_expr: + new = simple_expr.clone() + new.prefix = "" + simple_expr.replace(Call(Name("sorted"), [new], + prefix=simple_expr.prefix)) + else: + raise RuntimeError("should not have reached here") + sort_stmt.remove() + if next_stmt: + next_stmt[0].prefix = sort_stmt._prefix diff --git a/Lib/packaging/tests/pypi_server.py b/Lib/packaging/tests/pypi_server.py new file mode 100644 index 0000000..cc5fcca --- /dev/null +++ b/Lib/packaging/tests/pypi_server.py @@ -0,0 +1,444 @@ +"""Mock PyPI Server implementation, to use in tests. + +This module also provides a simple test case to extend if you need to use +the PyPIServer all along your test case. Be sure to read the documentation +before any use. + +XXX TODO: + +The mock server can handle simple HTTP request (to simulate a simple index) or +XMLRPC requests, over HTTP. Both does not have the same intergface to deal +with, and I think it's a pain. + +A good idea could be to re-think a bit the way dstributions are handled in the +mock server. As it should return malformed HTML pages, we need to keep the +static behavior. + +I think of something like that: + + >>> server = PyPIMockServer() + >>> server.startHTTP() + >>> server.startXMLRPC() + +Then, the server must have only one port to rely on, eg. + + >>> server.fulladress() + "http://ip:port/" + +It could be simple to have one HTTP server, relaying the requests to the two +implementations (static HTTP and XMLRPC over HTTP). +""" + +import os +import queue +import select +import socket +import threading +import socketserver +from functools import wraps +from http.server import HTTPServer, SimpleHTTPRequestHandler +from xmlrpc.server import SimpleXMLRPCServer + +from packaging.tests import unittest + +PYPI_DEFAULT_STATIC_PATH = os.path.join( + os.path.dirname(os.path.abspath(__file__)), 'pypiserver') + + +def use_xmlrpc_server(*server_args, **server_kwargs): + server_kwargs['serve_xmlrpc'] = True + return use_pypi_server(*server_args, **server_kwargs) + + +def use_http_server(*server_args, **server_kwargs): + server_kwargs['serve_xmlrpc'] = False + return use_pypi_server(*server_args, **server_kwargs) + + +def use_pypi_server(*server_args, **server_kwargs): + """Decorator to make use of the PyPIServer for test methods, + just when needed, and not for the entire duration of the testcase. + """ + def wrapper(func): + @wraps(func) + def wrapped(*args, **kwargs): + server = PyPIServer(*server_args, **server_kwargs) + server.start() + try: + func(server=server, *args, **kwargs) + finally: + server.stop() + return wrapped + return wrapper + + +class PyPIServerTestCase(unittest.TestCase): + + def setUp(self): + super(PyPIServerTestCase, self).setUp() + self.pypi = PyPIServer() + self.pypi.start() + self.addCleanup(self.pypi.stop) + + +class PyPIServer(threading.Thread): + """PyPI Mocked server. + Provides a mocked version of the PyPI API's, to ease tests. + + Support serving static content and serving previously given text. + """ + + def __init__(self, test_static_path=None, + static_filesystem_paths=["default"], + static_uri_paths=["simple", "packages"], serve_xmlrpc=False): + """Initialize the server. + + Default behavior is to start the HTTP server. You can either start the + xmlrpc server by setting xmlrpc to True. Caution: Only one server will + be started. + + static_uri_paths and static_base_path are parameters used to provides + respectively the http_paths to serve statically, and where to find the + matching files on the filesystem. + """ + # we want to launch the server in a new dedicated thread, to not freeze + # tests. + threading.Thread.__init__(self) + self._run = True + self._serve_xmlrpc = serve_xmlrpc + + #TODO allow to serve XMLRPC and HTTP static files at the same time. + if not self._serve_xmlrpc: + self.server = HTTPServer(('127.0.0.1', 0), PyPIRequestHandler) + self.server.RequestHandlerClass.pypi_server = self + + self.request_queue = queue.Queue() + self._requests = [] + self.default_response_status = 404 + self.default_response_headers = [('Content-type', 'text/plain')] + self.default_response_data = "The page does not exists" + + # initialize static paths / filesystems + self.static_uri_paths = static_uri_paths + + # append the static paths defined locally + if test_static_path is not None: + static_filesystem_paths.append(test_static_path) + self.static_filesystem_paths = [ + PYPI_DEFAULT_STATIC_PATH + "/" + path + for path in static_filesystem_paths] + else: + # XMLRPC server + self.server = PyPIXMLRPCServer(('127.0.0.1', 0)) + self.xmlrpc = XMLRPCMockIndex() + # register the xmlrpc methods + self.server.register_introspection_functions() + self.server.register_instance(self.xmlrpc) + + self.address = (self.server.server_name, self.server.server_port) + # to not have unwanted outputs. + self.server.RequestHandlerClass.log_request = lambda *_: None + + def run(self): + # loop because we can't stop it otherwise, for python < 2.6 + while self._run: + r, w, e = select.select([self.server], [], [], 0.5) + if r: + self.server.handle_request() + + def stop(self): + """self shutdown is not supported for python < 2.6""" + self._run = False + + def get_next_response(self): + return (self.default_response_status, + self.default_response_headers, + self.default_response_data) + + @property + def requests(self): + """Use this property to get all requests that have been made + to the server + """ + while True: + try: + self._requests.append(self.request_queue.get_nowait()) + except queue.Empty: + break + return self._requests + + @property + def full_address(self): + return "http://%s:%s" % self.address + + +class PyPIRequestHandler(SimpleHTTPRequestHandler): + # we need to access the pypi server while serving the content + pypi_server = None + + def serve_request(self): + """Serve the content. + + Also record the requests to be accessed later. If trying to access an + url matching a static uri, serve static content, otherwise serve + what is provided by the `get_next_response` method. + + If nothing is defined there, return a 404 header. + """ + # record the request. Read the input only on PUT or POST requests + if self.command in ("PUT", "POST"): + if 'content-length' in self.headers: + request_data = self.rfile.read( + int(self.headers['content-length'])) + else: + request_data = self.rfile.read() + + elif self.command in ("GET", "DELETE"): + request_data = '' + + self.pypi_server.request_queue.put((self, request_data)) + + # serve the content from local disc if we request an URL beginning + # by a pattern defined in `static_paths` + url_parts = self.path.split("/") + if (len(url_parts) > 1 and + url_parts[1] in self.pypi_server.static_uri_paths): + data = None + # always take the last first. + fs_paths = [] + fs_paths.extend(self.pypi_server.static_filesystem_paths) + fs_paths.reverse() + relative_path = self.path + for fs_path in fs_paths: + try: + if self.path.endswith("/"): + relative_path += "index.html" + + if relative_path.endswith('.tar.gz'): + with open(fs_path + relative_path, 'br') as file: + data = file.read() + headers = [('Content-type', 'application/x-gtar')] + else: + with open(fs_path + relative_path) as file: + data = file.read().encode() + headers = [('Content-type', 'text/html')] + + self.make_response(data, headers=headers) + + except IOError: + pass + + if data is None: + self.make_response("Not found", 404) + + # otherwise serve the content from get_next_response + else: + # send back a response + status, headers, data = self.pypi_server.get_next_response() + self.make_response(data, status, headers) + + do_POST = do_GET = do_DELETE = do_PUT = serve_request + + def make_response(self, data, status=200, + headers=[('Content-type', 'text/html')]): + """Send the response to the HTTP client""" + if not isinstance(status, int): + try: + status = int(status) + except ValueError: + # we probably got something like YYY Codename. + # Just get the first 3 digits + status = int(status[:3]) + + self.send_response(status) + for header, value in headers: + self.send_header(header, value) + self.end_headers() + + if type(data) is str: + data = data.encode() + + self.wfile.write(data) + + +class PyPIXMLRPCServer(SimpleXMLRPCServer): + def server_bind(self): + """Override server_bind to store the server name.""" + socketserver.TCPServer.server_bind(self) + host, port = self.socket.getsockname()[:2] + self.server_name = socket.getfqdn(host) + self.server_port = port + + +class MockDist: + """Fake distribution, used in the Mock PyPI Server""" + + def __init__(self, name, version="1.0", hidden=False, url="http://url/", + type="sdist", filename="", size=10000, + digest="123456", downloads=7, has_sig=False, + python_version="source", comment="comment", + author="John Doe", author_email="john@doe.name", + maintainer="Main Tayner", maintainer_email="maintainer_mail", + project_url="http://project_url/", homepage="http://homepage/", + keywords="", platform="UNKNOWN", classifiers=[], licence="", + description="Description", summary="Summary", stable_version="", + ordering="", documentation_id="", code_kwalitee_id="", + installability_id="", obsoletes=[], obsoletes_dist=[], + provides=[], provides_dist=[], requires=[], requires_dist=[], + requires_external=[], requires_python=""): + + # basic fields + self.name = name + self.version = version + self.hidden = hidden + + # URL infos + self.url = url + self.digest = digest + self.downloads = downloads + self.has_sig = has_sig + self.python_version = python_version + self.comment = comment + self.type = type + + # metadata + self.author = author + self.author_email = author_email + self.maintainer = maintainer + self.maintainer_email = maintainer_email + self.project_url = project_url + self.homepage = homepage + self.keywords = keywords + self.platform = platform + self.classifiers = classifiers + self.licence = licence + self.description = description + self.summary = summary + self.stable_version = stable_version + self.ordering = ordering + self.cheesecake_documentation_id = documentation_id + self.cheesecake_code_kwalitee_id = code_kwalitee_id + self.cheesecake_installability_id = installability_id + + self.obsoletes = obsoletes + self.obsoletes_dist = obsoletes_dist + self.provides = provides + self.provides_dist = provides_dist + self.requires = requires + self.requires_dist = requires_dist + self.requires_external = requires_external + self.requires_python = requires_python + + def url_infos(self): + return { + 'url': self.url, + 'packagetype': self.type, + 'filename': 'filename.tar.gz', + 'size': '6000', + 'md5_digest': self.digest, + 'downloads': self.downloads, + 'has_sig': self.has_sig, + 'python_version': self.python_version, + 'comment_text': self.comment, + } + + def metadata(self): + return { + 'maintainer': self.maintainer, + 'project_url': [self.project_url], + 'maintainer_email': self.maintainer_email, + 'cheesecake_code_kwalitee_id': self.cheesecake_code_kwalitee_id, + 'keywords': self.keywords, + 'obsoletes_dist': self.obsoletes_dist, + 'requires_external': self.requires_external, + 'author': self.author, + 'author_email': self.author_email, + 'download_url': self.url, + 'platform': self.platform, + 'version': self.version, + 'obsoletes': self.obsoletes, + 'provides': self.provides, + 'cheesecake_documentation_id': self.cheesecake_documentation_id, + '_pypi_hidden': self.hidden, + 'description': self.description, + '_pypi_ordering': 19, + 'requires_dist': self.requires_dist, + 'requires_python': self.requires_python, + 'classifiers': [], + 'name': self.name, + 'licence': self.licence, + 'summary': self.summary, + 'home_page': self.homepage, + 'stable_version': self.stable_version, + 'provides_dist': self.provides_dist or "%s (%s)" % (self.name, + self.version), + 'requires': self.requires, + 'cheesecake_installability_id': self.cheesecake_installability_id, + } + + def search_result(self): + return { + '_pypi_ordering': 0, + 'version': self.version, + 'name': self.name, + 'summary': self.summary, + } + + +class XMLRPCMockIndex: + """Mock XMLRPC server""" + + def __init__(self, dists=[]): + self._dists = dists + self._search_result = [] + + def add_distributions(self, dists): + for dist in dists: + self._dists.append(MockDist(**dist)) + + def set_distributions(self, dists): + self._dists = [] + self.add_distributions(dists) + + def set_search_result(self, result): + """set a predefined search result""" + self._search_result = result + + def _get_search_results(self): + results = [] + for name in self._search_result: + found_dist = [d for d in self._dists if d.name == name] + if found_dist: + results.append(found_dist[0]) + else: + dist = MockDist(name) + results.append(dist) + self._dists.append(dist) + return [r.search_result() for r in results] + + def list_packages(self): + return [d.name for d in self._dists] + + def package_releases(self, package_name, show_hidden=False): + if show_hidden: + # return all + return [d.version for d in self._dists if d.name == package_name] + else: + # return only un-hidden + return [d.version for d in self._dists if d.name == package_name + and not d.hidden] + + def release_urls(self, package_name, version): + return [d.url_infos() for d in self._dists + if d.name == package_name and d.version == version] + + def release_data(self, package_name, version): + release = [d for d in self._dists + if d.name == package_name and d.version == version] + if release: + return release[0].metadata() + else: + return {} + + def search(self, spec, operator="and"): + return self._get_search_results() diff --git a/Lib/packaging/tests/pypi_test_server.py b/Lib/packaging/tests/pypi_test_server.py new file mode 100644 index 0000000..8c8c641 --- /dev/null +++ b/Lib/packaging/tests/pypi_test_server.py @@ -0,0 +1,59 @@ +"""Test PyPI Server implementation at testpypi.python.org, to use in tests. + +This is a drop-in replacement for the mock pypi server for testing against a +real pypi server hosted by python.org especially for testing against. +""" + +import unittest + +PYPI_DEFAULT_STATIC_PATH = None + + +def use_xmlrpc_server(*server_args, **server_kwargs): + server_kwargs['serve_xmlrpc'] = True + return use_pypi_server(*server_args, **server_kwargs) + + +def use_http_server(*server_args, **server_kwargs): + server_kwargs['serve_xmlrpc'] = False + return use_pypi_server(*server_args, **server_kwargs) + + +def use_pypi_server(*server_args, **server_kwargs): + """Decorator to make use of the PyPIServer for test methods, + just when needed, and not for the entire duration of the testcase. + """ + def wrapper(func): + def wrapped(*args, **kwargs): + server = PyPIServer(*server_args, **server_kwargs) + func(server=server, *args, **kwargs) + return wrapped + return wrapper + + +class PyPIServerTestCase(unittest.TestCase): + + def setUp(self): + super(PyPIServerTestCase, self).setUp() + self.pypi = PyPIServer() + self.pypi.start() + self.addCleanup(self.pypi.stop) + + +class PyPIServer: + """Shim to access testpypi.python.org, for testing a real server.""" + + def __init__(self, test_static_path=None, + static_filesystem_paths=["default"], + static_uri_paths=["simple"], serve_xmlrpc=False): + self.address = ('testpypi.python.org', '80') + + def start(self): + pass + + def stop(self): + pass + + @property + def full_address(self): + return "http://%s:%s" % self.address diff --git a/Lib/packaging/tests/pypiserver/downloads_with_md5/packages/source/f/foobar/foobar-0.1.tar.gz b/Lib/packaging/tests/pypiserver/downloads_with_md5/packages/source/f/foobar/foobar-0.1.tar.gz Binary files differnew file mode 100644 index 0000000..333961e --- /dev/null +++ b/Lib/packaging/tests/pypiserver/downloads_with_md5/packages/source/f/foobar/foobar-0.1.tar.gz diff --git a/Lib/packaging/tests/pypiserver/downloads_with_md5/simple/badmd5/badmd5-0.1.tar.gz b/Lib/packaging/tests/pypiserver/downloads_with_md5/simple/badmd5/badmd5-0.1.tar.gz new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/downloads_with_md5/simple/badmd5/badmd5-0.1.tar.gz diff --git a/Lib/packaging/tests/pypiserver/downloads_with_md5/simple/badmd5/index.html b/Lib/packaging/tests/pypiserver/downloads_with_md5/simple/badmd5/index.html new file mode 100644 index 0000000..b89f1bd --- /dev/null +++ b/Lib/packaging/tests/pypiserver/downloads_with_md5/simple/badmd5/index.html @@ -0,0 +1,3 @@ +<html><body> +<a href="badmd5-0.1.tar.gz#md5=3e3d86693d6564c807272b11b3069dfe" rel="download">badmd5-0.1.tar.gz</a><br/> +</body></html> diff --git a/Lib/packaging/tests/pypiserver/downloads_with_md5/simple/foobar/index.html b/Lib/packaging/tests/pypiserver/downloads_with_md5/simple/foobar/index.html new file mode 100644 index 0000000..9e42b16 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/downloads_with_md5/simple/foobar/index.html @@ -0,0 +1,3 @@ +<html><body> +<a href="foobar-0.1.tar.gz#md5=fe18804c5b722ff024cabdf514924fc4" rel="download">foobar-0.1.tar.gz</a><br/> +</body></html> diff --git a/Lib/packaging/tests/pypiserver/downloads_with_md5/simple/index.html b/Lib/packaging/tests/pypiserver/downloads_with_md5/simple/index.html new file mode 100644 index 0000000..9baee04 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/downloads_with_md5/simple/index.html @@ -0,0 +1,2 @@ +<a href="foobar/">foobar/</a> +<a href="badmd5/">badmd5/</a> diff --git a/Lib/packaging/tests/pypiserver/foo_bar_baz/simple/bar/index.html b/Lib/packaging/tests/pypiserver/foo_bar_baz/simple/bar/index.html new file mode 100644 index 0000000..c3d42c5 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/foo_bar_baz/simple/bar/index.html @@ -0,0 +1,6 @@ +<html><head><title>Links for bar</title></head><body><h1>Links for bar</h1> +<a rel="download" href="../../packages/source/F/bar/bar-1.0.tar.gz">bar-1.0.tar.gz</a><br/> +<a rel="download" href="../../packages/source/F/bar/bar-1.0.1.tar.gz">bar-1.0.1.tar.gz</a><br/> +<a rel="download" href="../../packages/source/F/bar/bar-2.0.tar.gz">bar-2.0.tar.gz</a><br/> +<a rel="download" href="../../packages/source/F/bar/bar-2.0.1.tar.gz">bar-2.0.1.tar.gz</a><br/> +</body></html> diff --git a/Lib/packaging/tests/pypiserver/foo_bar_baz/simple/baz/index.html b/Lib/packaging/tests/pypiserver/foo_bar_baz/simple/baz/index.html new file mode 100644 index 0000000..4f34312 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/foo_bar_baz/simple/baz/index.html @@ -0,0 +1,6 @@ +<html><head><title>Links for baz</title></head><body><h1>Links for baz</h1> +<a rel="download" href="../../packages/source/F/baz/baz-1.0.tar.gz">baz-1.0.tar.gz</a><br/> +<a rel="download" href="../../packages/source/F/baz/baz-1.0.1.tar.gz">baz-1.0.1.tar.gz</a><br/> +<a rel="download" href="../../packages/source/F/baz/baz-2.0.tar.gz">baz-2.0.tar.gz</a><br/> +<a rel="download" href="../../packages/source/F/baz/baz-2.0.1.tar.gz">baz-2.0.1.tar.gz</a><br/> +</body></html> diff --git a/Lib/packaging/tests/pypiserver/foo_bar_baz/simple/foo/index.html b/Lib/packaging/tests/pypiserver/foo_bar_baz/simple/foo/index.html new file mode 100644 index 0000000..0565e11 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/foo_bar_baz/simple/foo/index.html @@ -0,0 +1,6 @@ +<html><head><title>Links for foo</title></head><body><h1>Links for foo</h1> +<a rel="download" href="../../packages/source/F/foo/foo-1.0.tar.gz">foo-1.0.tar.gz</a><br/> +<a rel="download" href="../../packages/source/F/foo/foo-1.0.1.tar.gz">foo-1.0.1.tar.gz</a><br/> +<a rel="download" href="../../packages/source/F/foo/foo-2.0.tar.gz">foo-2.0.tar.gz</a><br/> +<a rel="download" href="../../packages/source/F/foo/foo-2.0.1.tar.gz">foo-2.0.1.tar.gz</a><br/> +</body></html> diff --git a/Lib/packaging/tests/pypiserver/foo_bar_baz/simple/index.html b/Lib/packaging/tests/pypiserver/foo_bar_baz/simple/index.html new file mode 100644 index 0000000..a70cfd3 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/foo_bar_baz/simple/index.html @@ -0,0 +1,3 @@ +<a href="foo/">foo/</a> +<a href="bar/">bar/</a> +<a href="baz/">baz/</a> diff --git a/Lib/packaging/tests/pypiserver/project_list/simple/index.html b/Lib/packaging/tests/pypiserver/project_list/simple/index.html new file mode 100644 index 0000000..b36d728 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/project_list/simple/index.html @@ -0,0 +1,5 @@ +<a class="test" href="yeah">FooBar-bar</a> +<a class="test" href="yeah">Foobar-baz</a> +<a class="test" href="yeah">Baz-FooBar</a> +<a class="test" href="yeah">Baz</a> +<a class="test" href="yeah">Foo</a> diff --git a/Lib/packaging/tests/pypiserver/test_found_links/simple/foobar/index.html b/Lib/packaging/tests/pypiserver/test_found_links/simple/foobar/index.html new file mode 100644 index 0000000..a282a4e --- /dev/null +++ b/Lib/packaging/tests/pypiserver/test_found_links/simple/foobar/index.html @@ -0,0 +1,6 @@ +<html><head><title>Links for Foobar</title></head><body><h1>Links for Foobar</h1> +<a rel="download" href="../../packages/source/F/Foobar/Foobar-1.0.tar.gz#md5=98fa833fdabcdd78d00245aead66c174">Foobar-1.0.tar.gz</a><br/> +<a rel="download" href="../../packages/source/F/Foobar/Foobar-1.0.1.tar.gz#md5=2351efb20f6b7b5d9ce80fa4cb1bd9ca">Foobar-1.0.1.tar.gz</a><br/> +<a rel="download" href="../../packages/source/F/Foobar/Foobar-2.0.tar.gz#md5=98fa833fdabcdd78d00245aead66c274">Foobar-2.0.tar.gz</a><br/> +<a rel="download" href="../../packages/source/F/Foobar/Foobar-2.0.1.tar.gz#md5=2352efb20f6b7b5d9ce80fa4cb2bd9ca">Foobar-2.0.1.tar.gz</a><br/> +</body></html> diff --git a/Lib/packaging/tests/pypiserver/test_found_links/simple/index.html b/Lib/packaging/tests/pypiserver/test_found_links/simple/index.html new file mode 100644 index 0000000..a1a7bb7 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/test_found_links/simple/index.html @@ -0,0 +1 @@ +<a href="foobar/">foobar/</a> diff --git a/Lib/packaging/tests/pypiserver/test_pypi_server/external/index.html b/Lib/packaging/tests/pypiserver/test_pypi_server/external/index.html new file mode 100644 index 0000000..265ee0a --- /dev/null +++ b/Lib/packaging/tests/pypiserver/test_pypi_server/external/index.html @@ -0,0 +1 @@ +index.html from external server diff --git a/Lib/packaging/tests/pypiserver/test_pypi_server/simple/index.html b/Lib/packaging/tests/pypiserver/test_pypi_server/simple/index.html new file mode 100644 index 0000000..6f97667 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/test_pypi_server/simple/index.html @@ -0,0 +1 @@ +Yeah diff --git a/Lib/packaging/tests/pypiserver/with_externals/external/external.html b/Lib/packaging/tests/pypiserver/with_externals/external/external.html new file mode 100644 index 0000000..92e4702 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/with_externals/external/external.html @@ -0,0 +1,3 @@ +<html><body> +<a href="/foobar-0.1.tar.gz#md5=1__bad_md5___">bad old link</a> +</body></html> diff --git a/Lib/packaging/tests/pypiserver/with_externals/simple/foobar/index.html b/Lib/packaging/tests/pypiserver/with_externals/simple/foobar/index.html new file mode 100644 index 0000000..b100a26 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/with_externals/simple/foobar/index.html @@ -0,0 +1,4 @@ +<html><body> +<a rel ="download" href="/foobar-0.1.tar.gz#md5=12345678901234567">foobar-0.1.tar.gz</a><br/> +<a href="../../external/external.html" rel="homepage">external homepage</a><br/> +</body></html> diff --git a/Lib/packaging/tests/pypiserver/with_externals/simple/index.html b/Lib/packaging/tests/pypiserver/with_externals/simple/index.html new file mode 100644 index 0000000..a1a7bb7 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/with_externals/simple/index.html @@ -0,0 +1 @@ +<a href="foobar/">foobar/</a> diff --git a/Lib/packaging/tests/pypiserver/with_norel_links/external/homepage.html b/Lib/packaging/tests/pypiserver/with_norel_links/external/homepage.html new file mode 100644 index 0000000..1cc0c32 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/with_norel_links/external/homepage.html @@ -0,0 +1,7 @@ +<html> +<body> +<p>a rel=homepage HTML page</p> +<a href="/foobar-2.0.tar.gz">foobar 2.0</a> +</body> +</html> + diff --git a/Lib/packaging/tests/pypiserver/with_norel_links/external/nonrel.html b/Lib/packaging/tests/pypiserver/with_norel_links/external/nonrel.html new file mode 100644 index 0000000..f6ace22 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/with_norel_links/external/nonrel.html @@ -0,0 +1 @@ +A page linked without rel="download" or rel="homepage" link. diff --git a/Lib/packaging/tests/pypiserver/with_norel_links/simple/foobar/index.html b/Lib/packaging/tests/pypiserver/with_norel_links/simple/foobar/index.html new file mode 100644 index 0000000..171df93 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/with_norel_links/simple/foobar/index.html @@ -0,0 +1,6 @@ +<html><body> +<a rel="download" href="/foobar-0.1.tar.gz" rel="download">foobar-0.1.tar.gz</a><br/> +<a href="../../external/homepage.html" rel="homepage">external homepage</a><br/> +<a href="../../external/nonrel.html">unrelated link</a><br/> +<a href="/unrelated-0.2.tar.gz">unrelated download</a></br/> +</body></html> diff --git a/Lib/packaging/tests/pypiserver/with_norel_links/simple/index.html b/Lib/packaging/tests/pypiserver/with_norel_links/simple/index.html new file mode 100644 index 0000000..a1a7bb7 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/with_norel_links/simple/index.html @@ -0,0 +1 @@ +<a href="foobar/">foobar/</a> diff --git a/Lib/packaging/tests/pypiserver/with_real_externals/simple/foobar/index.html b/Lib/packaging/tests/pypiserver/with_real_externals/simple/foobar/index.html new file mode 100644 index 0000000..b2885ae --- /dev/null +++ b/Lib/packaging/tests/pypiserver/with_real_externals/simple/foobar/index.html @@ -0,0 +1,4 @@ +<html><body> +<a rel="download" href="/foobar-0.1.tar.gz#md5=0_correct_md5">foobar-0.1.tar.gz</a><br/> +<a href="http://a-really-external-website/external/external.html" rel="homepage">external homepage</a><br/> +</body></html> diff --git a/Lib/packaging/tests/pypiserver/with_real_externals/simple/index.html b/Lib/packaging/tests/pypiserver/with_real_externals/simple/index.html new file mode 100644 index 0000000..a1a7bb7 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/with_real_externals/simple/index.html @@ -0,0 +1 @@ +<a href="foobar/">foobar/</a> diff --git a/Lib/packaging/tests/support.py b/Lib/packaging/tests/support.py new file mode 100644 index 0000000..cf5d788 --- /dev/null +++ b/Lib/packaging/tests/support.py @@ -0,0 +1,259 @@ +"""Support code for packaging test cases. + +A few helper classes are provided: LoggingCatcher, TempdirManager and +EnvironRestorer. They are written to be used as mixins:: + + from packaging.tests import unittest + from packaging.tests.support import LoggingCatcher + + class SomeTestCase(LoggingCatcher, unittest.TestCase): + +If you need to define a setUp method on your test class, you have to +call the mixin class' setUp method or it won't work (same thing for +tearDown): + + def setUp(self): + super(SomeTestCase, self).setUp() + ... # other setup code + +Also provided is a DummyCommand class, useful to mock commands in the +tests of another command that needs them, a create_distribution function +and a skip_unless_symlink decorator. + +Also provided is a DummyCommand class, useful to mock commands in the +tests of another command that needs them, a create_distribution function +and a skip_unless_symlink decorator. + +Each class or function has a docstring to explain its purpose and usage. +""" + +import os +import errno +import shutil +import logging +import weakref +import tempfile + +from packaging import logger +from packaging.dist import Distribution +from packaging.tests import unittest + +__all__ = ['LoggingCatcher', 'TempdirManager', 'EnvironRestorer', + 'DummyCommand', 'unittest', 'create_distribution', + 'skip_unless_symlink'] + + +class _TestHandler(logging.handlers.BufferingHandler): + # stolen and adapted from test.support + + def __init__(self): + logging.handlers.BufferingHandler.__init__(self, 0) + self.setLevel(logging.DEBUG) + + def shouldFlush(self): + return False + + def emit(self, record): + self.buffer.append(record) + + +class LoggingCatcher: + """TestCase-compatible mixin to receive logging calls. + + Upon setUp, instances of this classes get a BufferingHandler that's + configured to record all messages logged to the 'packaging' logger. + + Use get_logs to retrieve messages and self.loghandler.flush to discard + them. + """ + + def setUp(self): + super(LoggingCatcher, self).setUp() + self.loghandler = handler = _TestHandler() + logger.addHandler(handler) + self.addCleanup(logger.setLevel, logger.level) + logger.setLevel(logging.DEBUG) # we want all messages + + def tearDown(self): + handler = self.loghandler + # All this is necessary to properly shut down the logging system and + # avoid a regrtest complaint. Thanks to Vinay Sajip for the help. + handler.close() + logger.removeHandler(handler) + for ref in weakref.getweakrefs(handler): + logging._removeHandlerRef(ref) + del self.loghandler + super(LoggingCatcher, self).tearDown() + + def get_logs(self, *levels): + """Return all log messages with level in *levels*. + + Without explicit levels given, returns all messages. + *levels* defaults to all levels. For log calls with arguments (i.e. + logger.info('bla bla %s', arg)), the messages + Returns a list. + + Example: self.get_logs(logging.WARN, logging.DEBUG). + """ + if not levels: + return [log.getMessage() for log in self.loghandler.buffer] + return [log.getMessage() for log in self.loghandler.buffer + if log.levelno in levels] + + +class TempdirManager: + """TestCase-compatible mixin to create temporary directories and files. + + Directories and files created in a test_* method will be removed after it + has run. + """ + + def setUp(self): + super(TempdirManager, self).setUp() + self._basetempdir = tempfile.mkdtemp() + + def tearDown(self): + shutil.rmtree(self._basetempdir, os.name in ('nt', 'cygwin')) + super(TempdirManager, self).tearDown() + + def mktempfile(self): + """Create a read-write temporary file and return it.""" + + def _delete_file(filename): + try: + os.remove(filename) + except OSError as exc: + if exc.errno != errno.ENOENT: + raise + + fd, fn = tempfile.mkstemp(dir=self._basetempdir) + os.close(fd) + fp = open(fn, 'w+') + self.addCleanup(fp.close) + self.addCleanup(_delete_file, fn) + return fp + + def mkdtemp(self): + """Create a temporary directory and return its path.""" + d = tempfile.mkdtemp(dir=self._basetempdir) + return d + + def write_file(self, path, content='xxx'): + """Write a file at the given path. + + path can be a string, a tuple or a list; if it's a tuple or list, + os.path.join will be used to produce a path. + """ + if isinstance(path, (list, tuple)): + path = os.path.join(*path) + f = open(path, 'w') + try: + f.write(content) + finally: + f.close() + + def create_dist(self, **kw): + """Create a stub distribution object and files. + + This function creates a Distribution instance (use keyword arguments + to customize it) and a temporary directory with a project structure + (currently an empty directory). + + It returns the path to the directory and the Distribution instance. + You can use self.write_file to write any file in that + directory, e.g. setup scripts or Python modules. + """ + if 'name' not in kw: + kw['name'] = 'foo' + tmp_dir = self.mkdtemp() + project_dir = os.path.join(tmp_dir, kw['name']) + os.mkdir(project_dir) + dist = Distribution(attrs=kw) + return project_dir, dist + + def assertIsFile(self, *args): + path = os.path.join(*args) + dirname = os.path.dirname(path) + file = os.path.basename(path) + if os.path.isdir(dirname): + files = os.listdir(dirname) + msg = "%s not found in %s: %s" % (file, dirname, files) + assert os.path.isfile(path), msg + else: + raise AssertionError( + '%s not found. %s does not exist' % (file, dirname)) + + def assertIsNotFile(self, *args): + path = os.path.join(*args) + self.assertFalse(os.path.isfile(path), "%r exists" % path) + + +class EnvironRestorer: + """TestCase-compatible mixin to restore or delete environment variables. + + The variables to restore (or delete if they were not originally present) + must be explicitly listed in self.restore_environ. It's better to be + aware of what we're modifying instead of saving and restoring the whole + environment. + """ + + def setUp(self): + super(EnvironRestorer, self).setUp() + self._saved = [] + self._added = [] + for key in self.restore_environ: + if key in os.environ: + self._saved.append((key, os.environ[key])) + else: + self._added.append(key) + + def tearDown(self): + for key, value in self._saved: + os.environ[key] = value + for key in self._added: + os.environ.pop(key, None) + super(EnvironRestorer, self).tearDown() + + +class DummyCommand: + """Class to store options for retrieval via set_undefined_options(). + + Useful for mocking one dependency command in the tests for another + command, see e.g. the dummy build command in test_build_scripts. + """ + + def __init__(self, **kwargs): + for kw, val in kwargs.items(): + setattr(self, kw, val) + + def ensure_finalized(self): + pass + + +class TestDistribution(Distribution): + """Distribution subclasses that avoids the default search for + configuration files. + + The ._config_files attribute must be set before + .parse_config_files() is called. + """ + + def find_config_files(self): + return self._config_files + + +def create_distribution(configfiles=()): + """Prepares a distribution with given config files parsed.""" + d = TestDistribution() + d.config.find_config_files = d.find_config_files + d._config_files = configfiles + d.parse_config_files() + d.parse_command_line() + return d + + +try: + from test.support import skip_unless_symlink +except ImportError: + skip_unless_symlink = unittest.skip( + 'requires test.support.skip_unless_symlink') diff --git a/Lib/packaging/tests/test_ccompiler.py b/Lib/packaging/tests/test_ccompiler.py new file mode 100644 index 0000000..dd4bdd9 --- /dev/null +++ b/Lib/packaging/tests/test_ccompiler.py @@ -0,0 +1,15 @@ +"""Tests for distutils.compiler.ccompiler.""" + +from packaging.compiler import ccompiler +from packaging.tests import unittest, support + + +class CCompilerTestCase(unittest.TestCase): + pass # XXX need some tests on CCompiler + + +def test_suite(): + return unittest.makeSuite(CCompilerTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_bdist.py b/Lib/packaging/tests/test_command_bdist.py new file mode 100644 index 0000000..1522b7e --- /dev/null +++ b/Lib/packaging/tests/test_command_bdist.py @@ -0,0 +1,77 @@ +"""Tests for distutils.command.bdist.""" + +from packaging import util +from packaging.command.bdist import bdist, show_formats + +from packaging.tests import unittest, support, captured_stdout + + +class BuildTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + def _mock_get_platform(self): + self._get_platform_called = True + return self._get_platform() + + def setUp(self): + super(BuildTestCase, self).setUp() + + # mock util.get_platform + self._get_platform_called = False + self._get_platform = util.get_platform + util.get_platform = self._mock_get_platform + + def tearDown(self): + super(BuildTestCase, self).tearDown() + util.get_platform = self._get_platform + + def test_formats(self): + + # let's create a command and make sure + # we can fix the format + pkg_pth, dist = self.create_dist() + cmd = bdist(dist) + cmd.formats = ['msi'] + cmd.ensure_finalized() + self.assertEqual(cmd.formats, ['msi']) + + # what format bdist offers ? + # XXX an explicit list in bdist is + # not the best way to bdist_* commands + # we should add a registry + formats = sorted(('zip', 'gztar', 'bztar', 'ztar', + 'tar', 'wininst', 'msi')) + found = sorted(cmd.format_command) + self.assertEqual(found, formats) + + def test_skip_build(self): + pkg_pth, dist = self.create_dist() + cmd = bdist(dist) + cmd.skip_build = False + cmd.formats = ['ztar'] + cmd.ensure_finalized() + self.assertFalse(self._get_platform_called) + + pkg_pth, dist = self.create_dist() + cmd = bdist(dist) + cmd.skip_build = True + cmd.formats = ['ztar'] + cmd.ensure_finalized() + self.assertTrue(self._get_platform_called) + + def test_show_formats(self): + __, stdout = captured_stdout(show_formats) + + # the output should be a header line + one line per format + num_formats = len(bdist.format_commands) + output = [line for line in stdout.split('\n') + if line.strip().startswith('--formats=')] + self.assertEqual(len(output), num_formats) + + +def test_suite(): + return unittest.makeSuite(BuildTestCase) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/Lib/packaging/tests/test_command_bdist_dumb.py b/Lib/packaging/tests/test_command_bdist_dumb.py new file mode 100644 index 0000000..ce1563f --- /dev/null +++ b/Lib/packaging/tests/test_command_bdist_dumb.py @@ -0,0 +1,103 @@ +"""Tests for distutils.command.bdist_dumb.""" + +import sys +import os + +# zlib is not used here, but if it's not available +# test_simple_built will fail +try: + import zlib +except ImportError: + zlib = None + +from packaging.dist import Distribution +from packaging.command.bdist_dumb import bdist_dumb +from packaging.tests import unittest, support + + +SETUP_PY = """\ +from distutils.run import setup +import foo + +setup(name='foo', version='0.1', py_modules=['foo'], + url='xxx', author='xxx', author_email='xxx') +""" + + +class BuildDumbTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + def setUp(self): + super(BuildDumbTestCase, self).setUp() + self.old_location = os.getcwd() + self.old_sys_argv = sys.argv, sys.argv[:] + + def tearDown(self): + os.chdir(self.old_location) + sys.argv = self.old_sys_argv[0] + sys.argv[:] = self.old_sys_argv[1] + super(BuildDumbTestCase, self).tearDown() + + @unittest.skipUnless(zlib, "requires zlib") + def test_simple_built(self): + + # let's create a simple package + tmp_dir = self.mkdtemp() + pkg_dir = os.path.join(tmp_dir, 'foo') + os.mkdir(pkg_dir) + self.write_file((pkg_dir, 'setup.py'), SETUP_PY) + self.write_file((pkg_dir, 'foo.py'), '#') + self.write_file((pkg_dir, 'MANIFEST.in'), 'include foo.py') + self.write_file((pkg_dir, 'README'), '') + + dist = Distribution({'name': 'foo', 'version': '0.1', + 'py_modules': ['foo'], + 'url': 'xxx', 'author': 'xxx', + 'author_email': 'xxx'}) + dist.script_name = 'setup.py' + os.chdir(pkg_dir) + + sys.argv[:] = ['setup.py'] + cmd = bdist_dumb(dist) + + # so the output is the same no matter + # what is the platform + cmd.format = 'zip' + + cmd.ensure_finalized() + cmd.run() + + # see what we have + dist_created = os.listdir(os.path.join(pkg_dir, 'dist')) + base = "%s.%s" % (dist.get_fullname(), cmd.plat_name) + if os.name == 'os2': + base = base.replace(':', '-') + + wanted = ['%s.zip' % base] + self.assertEqual(dist_created, wanted) + + # now let's check what we have in the zip file + # XXX to be done + + def test_finalize_options(self): + pkg_dir, dist = self.create_dist() + os.chdir(pkg_dir) + cmd = bdist_dumb(dist) + self.assertEqual(cmd.bdist_dir, None) + cmd.finalize_options() + + # bdist_dir is initialized to bdist_base/dumb if not set + base = cmd.get_finalized_command('bdist').bdist_base + self.assertEqual(cmd.bdist_dir, os.path.join(base, 'dumb')) + + # the format is set to a default value depending on the os.name + default = cmd.default_format[os.name] + self.assertEqual(cmd.format, default) + + +def test_suite(): + return unittest.makeSuite(BuildDumbTestCase) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/Lib/packaging/tests/test_command_bdist_msi.py b/Lib/packaging/tests/test_command_bdist_msi.py new file mode 100644 index 0000000..fded962 --- /dev/null +++ b/Lib/packaging/tests/test_command_bdist_msi.py @@ -0,0 +1,25 @@ +"""Tests for distutils.command.bdist_msi.""" +import sys + +from packaging.tests import unittest, support + + +class BDistMSITestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + @unittest.skipUnless(sys.platform == "win32", "runs only on win32") + def test_minimal(self): + # minimal test XXX need more tests + from packaging.command.bdist_msi import bdist_msi + pkg_pth, dist = self.create_dist() + cmd = bdist_msi(dist) + cmd.ensure_finalized() + + +def test_suite(): + return unittest.makeSuite(BDistMSITestCase) + + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/Lib/packaging/tests/test_command_bdist_wininst.py b/Lib/packaging/tests/test_command_bdist_wininst.py new file mode 100644 index 0000000..09bdaad --- /dev/null +++ b/Lib/packaging/tests/test_command_bdist_wininst.py @@ -0,0 +1,32 @@ +"""Tests for distutils.command.bdist_wininst.""" + +from packaging.command.bdist_wininst import bdist_wininst +from packaging.tests import unittest, support + + +class BuildWinInstTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + def test_get_exe_bytes(self): + + # issue5731: command was broken on non-windows platforms + # this test makes sure it works now for every platform + # let's create a command + pkg_pth, dist = self.create_dist() + cmd = bdist_wininst(dist) + cmd.ensure_finalized() + + # let's run the code that finds the right wininst*.exe file + # and make sure it finds it and returns its content + # no matter what platform we have + exe_file = cmd.get_exe_bytes() + self.assertGreater(len(exe_file), 10) + + +def test_suite(): + return unittest.makeSuite(BuildWinInstTestCase) + + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/Lib/packaging/tests/test_command_build.py b/Lib/packaging/tests/test_command_build.py new file mode 100644 index 0000000..91fbe42 --- /dev/null +++ b/Lib/packaging/tests/test_command_build.py @@ -0,0 +1,55 @@ +"""Tests for distutils.command.build.""" +import os +import sys + +from packaging.command.build import build +from sysconfig import get_platform +from packaging.tests import unittest, support + + +class BuildTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + def test_finalize_options(self): + pkg_dir, dist = self.create_dist() + cmd = build(dist) + cmd.finalize_options() + + # if not specified, plat_name gets the current platform + self.assertEqual(cmd.plat_name, get_platform()) + + # build_purelib is build + lib + wanted = os.path.join(cmd.build_base, 'lib') + self.assertEqual(cmd.build_purelib, wanted) + + # build_platlib is 'build/lib.platform-x.x[-pydebug]' + # examples: + # build/lib.macosx-10.3-i386-2.7 + plat_spec = '.%s-%s' % (cmd.plat_name, sys.version[0:3]) + if hasattr(sys, 'gettotalrefcount'): + self.assertTrue(cmd.build_platlib.endswith('-pydebug')) + plat_spec += '-pydebug' + wanted = os.path.join(cmd.build_base, 'lib' + plat_spec) + self.assertEqual(cmd.build_platlib, wanted) + + # by default, build_lib = build_purelib + self.assertEqual(cmd.build_lib, cmd.build_purelib) + + # build_temp is build/temp.<plat> + wanted = os.path.join(cmd.build_base, 'temp' + plat_spec) + self.assertEqual(cmd.build_temp, wanted) + + # build_scripts is build/scripts-x.x + wanted = os.path.join(cmd.build_base, 'scripts-' + sys.version[0:3]) + self.assertEqual(cmd.build_scripts, wanted) + + # executable is os.path.normpath(sys.executable) + self.assertEqual(cmd.executable, os.path.normpath(sys.executable)) + + +def test_suite(): + return unittest.makeSuite(BuildTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_build_clib.py b/Lib/packaging/tests/test_command_build_clib.py new file mode 100644 index 0000000..a2a8583 --- /dev/null +++ b/Lib/packaging/tests/test_command_build_clib.py @@ -0,0 +1,141 @@ +"""Tests for distutils.command.build_clib.""" +import os +import sys + +from packaging.util import find_executable +from packaging.command.build_clib import build_clib +from packaging.errors import PackagingSetupError +from packaging.tests import unittest, support + + +class BuildCLibTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + def test_check_library_dist(self): + pkg_dir, dist = self.create_dist() + cmd = build_clib(dist) + + # 'libraries' option must be a list + self.assertRaises(PackagingSetupError, cmd.check_library_list, 'foo') + + # each element of 'libraries' must a 2-tuple + self.assertRaises(PackagingSetupError, cmd.check_library_list, + ['foo1', 'foo2']) + + # first element of each tuple in 'libraries' + # must be a string (the library name) + self.assertRaises(PackagingSetupError, cmd.check_library_list, + [(1, 'foo1'), ('name', 'foo2')]) + + # library name may not contain directory separators + self.assertRaises(PackagingSetupError, cmd.check_library_list, + [('name', 'foo1'), + ('another/name', 'foo2')]) + + # second element of each tuple must be a dictionary (build info) + self.assertRaises(PackagingSetupError, cmd.check_library_list, + [('name', {}), + ('another', 'foo2')]) + + # those work + libs = [('name', {}), ('name', {'ok': 'good'})] + cmd.check_library_list(libs) + + def test_get_source_files(self): + pkg_dir, dist = self.create_dist() + cmd = build_clib(dist) + + # "in 'libraries' option 'sources' must be present and must be + # a list of source filenames + cmd.libraries = [('name', {})] + self.assertRaises(PackagingSetupError, cmd.get_source_files) + + cmd.libraries = [('name', {'sources': 1})] + self.assertRaises(PackagingSetupError, cmd.get_source_files) + + cmd.libraries = [('name', {'sources': ['a', 'b']})] + self.assertEqual(cmd.get_source_files(), ['a', 'b']) + + cmd.libraries = [('name', {'sources': ('a', 'b')})] + self.assertEqual(cmd.get_source_files(), ['a', 'b']) + + cmd.libraries = [('name', {'sources': ('a', 'b')}), + ('name2', {'sources': ['c', 'd']})] + self.assertEqual(cmd.get_source_files(), ['a', 'b', 'c', 'd']) + + def test_build_libraries(self): + pkg_dir, dist = self.create_dist() + cmd = build_clib(dist) + + class FakeCompiler: + def compile(*args, **kw): + pass + create_static_lib = compile + + cmd.compiler = FakeCompiler() + + # build_libraries is also doing a bit of type checking + lib = [('name', {'sources': 'notvalid'})] + self.assertRaises(PackagingSetupError, cmd.build_libraries, lib) + + lib = [('name', {'sources': []})] + cmd.build_libraries(lib) + + lib = [('name', {'sources': ()})] + cmd.build_libraries(lib) + + def test_finalize_options(self): + pkg_dir, dist = self.create_dist() + cmd = build_clib(dist) + + cmd.include_dirs = 'one-dir' + cmd.finalize_options() + self.assertEqual(cmd.include_dirs, ['one-dir']) + + cmd.include_dirs = None + cmd.finalize_options() + self.assertEqual(cmd.include_dirs, []) + + cmd.distribution.libraries = 'WONTWORK' + self.assertRaises(PackagingSetupError, cmd.finalize_options) + + @unittest.skipIf(sys.platform == 'win32', 'disabled on win32') + def test_run(self): + pkg_dir, dist = self.create_dist() + cmd = build_clib(dist) + + foo_c = os.path.join(pkg_dir, 'foo.c') + self.write_file(foo_c, 'int main(void) { return 1;}\n') + cmd.libraries = [('foo', {'sources': [foo_c]})] + + build_temp = os.path.join(pkg_dir, 'build') + os.mkdir(build_temp) + cmd.build_temp = build_temp + cmd.build_clib = build_temp + + # before we run the command, we want to make sure + # all commands are present on the system + # by creating a compiler and checking its executables + from packaging.compiler import new_compiler, customize_compiler + + compiler = new_compiler() + customize_compiler(compiler) + for ccmd in compiler.executables.values(): + if ccmd is None: + continue + if find_executable(ccmd[0]) is None: + raise unittest.SkipTest("can't test") + + # this should work + cmd.run() + + # let's check the result + self.assertIn('libfoo.a', os.listdir(build_temp)) + + +def test_suite(): + return unittest.makeSuite(BuildCLibTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_build_ext.py b/Lib/packaging/tests/test_command_build_ext.py new file mode 100644 index 0000000..2d79842 --- /dev/null +++ b/Lib/packaging/tests/test_command_build_ext.py @@ -0,0 +1,353 @@ +import os +import sys +import site +import shutil +import sysconfig +from io import StringIO +from packaging.dist import Distribution +from packaging.errors import UnknownFileError, CompileError +from packaging.command.build_ext import build_ext +from packaging.compiler.extension import Extension + +from packaging.tests import support, unittest, verbose, unload + +# http://bugs.python.org/issue4373 +# Don't load the xx module more than once. +ALREADY_TESTED = False + + +def _get_source_filename(): + srcdir = sysconfig.get_config_var('srcdir') + return os.path.join(srcdir, 'Modules', 'xxmodule.c') + + +class BuildExtTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + def setUp(self): + # Create a simple test environment + # Note that we're making changes to sys.path + super(BuildExtTestCase, self).setUp() + self.tmp_dir = self.mkdtemp() + self.sys_path = sys.path, sys.path[:] + sys.path.append(self.tmp_dir) + shutil.copy(_get_source_filename(), self.tmp_dir) + self.old_user_base = site.USER_BASE + site.USER_BASE = self.mkdtemp() + build_ext.USER_BASE = site.USER_BASE + + def test_build_ext(self): + global ALREADY_TESTED + xx_c = os.path.join(self.tmp_dir, 'xxmodule.c') + xx_ext = Extension('xx', [xx_c]) + dist = Distribution({'name': 'xx', 'ext_modules': [xx_ext]}) + dist.package_dir = self.tmp_dir + cmd = build_ext(dist) + if os.name == "nt": + # On Windows, we must build a debug version iff running + # a debug build of Python + cmd.debug = sys.executable.endswith("_d.exe") + cmd.build_lib = self.tmp_dir + cmd.build_temp = self.tmp_dir + + old_stdout = sys.stdout + if not verbose: + # silence compiler output + sys.stdout = StringIO() + try: + cmd.ensure_finalized() + cmd.run() + finally: + sys.stdout = old_stdout + + if ALREADY_TESTED: + return + else: + ALREADY_TESTED = True + + import xx + + for attr in ('error', 'foo', 'new', 'roj'): + self.assertTrue(hasattr(xx, attr)) + + self.assertEqual(xx.foo(2, 5), 7) + self.assertEqual(xx.foo(13, 15), 28) + self.assertEqual(xx.new().demo(), None) + doc = 'This is a template module just for instruction.' + self.assertEqual(xx.__doc__, doc) + self.assertTrue(isinstance(xx.Null(), xx.Null)) + self.assertTrue(isinstance(xx.Str(), xx.Str)) + + def tearDown(self): + # Get everything back to normal + unload('xx') + sys.path = self.sys_path[0] + sys.path[:] = self.sys_path[1] + if sys.version > "2.6": + site.USER_BASE = self.old_user_base + build_ext.USER_BASE = self.old_user_base + + super(BuildExtTestCase, self).tearDown() + + def test_solaris_enable_shared(self): + dist = Distribution({'name': 'xx'}) + cmd = build_ext(dist) + old = sys.platform + + sys.platform = 'sunos' # fooling finalize_options + from sysconfig import _CONFIG_VARS + + old_var = _CONFIG_VARS.get('Py_ENABLE_SHARED') + _CONFIG_VARS['Py_ENABLE_SHARED'] = 1 + try: + cmd.ensure_finalized() + finally: + sys.platform = old + if old_var is None: + del _CONFIG_VARS['Py_ENABLE_SHARED'] + else: + _CONFIG_VARS['Py_ENABLE_SHARED'] = old_var + + # make sure we get some library dirs under solaris + self.assertGreater(len(cmd.library_dirs), 0) + + @unittest.skipIf(sys.version < '2.6', 'requires Python 2.6 or higher') + def test_user_site(self): + dist = Distribution({'name': 'xx'}) + cmd = build_ext(dist) + + # making sure the user option is there + options = [name for name, short, label in + cmd.user_options] + self.assertIn('user', options) + + # setting a value + cmd.user = True + + # setting user based lib and include + lib = os.path.join(site.USER_BASE, 'lib') + incl = os.path.join(site.USER_BASE, 'include') + os.mkdir(lib) + os.mkdir(incl) + + # let's run finalize + cmd.ensure_finalized() + + # see if include_dirs and library_dirs + # were set + self.assertIn(lib, cmd.library_dirs) + self.assertIn(lib, cmd.rpath) + self.assertIn(incl, cmd.include_dirs) + + def test_optional_extension(self): + + # this extension will fail, but let's ignore this failure + # with the optional argument. + modules = [Extension('foo', ['xxx'], optional=False)] + dist = Distribution({'name': 'xx', 'ext_modules': modules}) + cmd = build_ext(dist) + cmd.ensure_finalized() + self.assertRaises((UnknownFileError, CompileError), + cmd.run) # should raise an error + + modules = [Extension('foo', ['xxx'], optional=True)] + dist = Distribution({'name': 'xx', 'ext_modules': modules}) + cmd = build_ext(dist) + cmd.ensure_finalized() + cmd.run() # should pass + + def test_finalize_options(self): + # Make sure Python's include directories (for Python.h, pyconfig.h, + # etc.) are in the include search path. + modules = [Extension('foo', ['xxx'], optional=False)] + dist = Distribution({'name': 'xx', 'ext_modules': modules}) + cmd = build_ext(dist) + cmd.finalize_options() + + py_include = sysconfig.get_path('include') + self.assertIn(py_include, cmd.include_dirs) + + plat_py_include = sysconfig.get_path('platinclude') + self.assertIn(plat_py_include, cmd.include_dirs) + + # make sure cmd.libraries is turned into a list + # if it's a string + cmd = build_ext(dist) + cmd.libraries = 'my_lib' + cmd.finalize_options() + self.assertEqual(cmd.libraries, ['my_lib']) + + # make sure cmd.library_dirs is turned into a list + # if it's a string + cmd = build_ext(dist) + cmd.library_dirs = 'my_lib_dir' + cmd.finalize_options() + self.assertIn('my_lib_dir', cmd.library_dirs) + + # make sure rpath is turned into a list + # if it's a list of os.pathsep's paths + cmd = build_ext(dist) + cmd.rpath = os.pathsep.join(['one', 'two']) + cmd.finalize_options() + self.assertEqual(cmd.rpath, ['one', 'two']) + + # XXX more tests to perform for win32 + + # make sure define is turned into 2-tuples + # strings if they are ','-separated strings + cmd = build_ext(dist) + cmd.define = 'one,two' + cmd.finalize_options() + self.assertEqual(cmd.define, [('one', '1'), ('two', '1')]) + + # make sure undef is turned into a list of + # strings if they are ','-separated strings + cmd = build_ext(dist) + cmd.undef = 'one,two' + cmd.finalize_options() + self.assertEqual(cmd.undef, ['one', 'two']) + + # make sure swig_opts is turned into a list + cmd = build_ext(dist) + cmd.swig_opts = None + cmd.finalize_options() + self.assertEqual(cmd.swig_opts, []) + + cmd = build_ext(dist) + cmd.swig_opts = '1 2' + cmd.finalize_options() + self.assertEqual(cmd.swig_opts, ['1', '2']) + + def test_get_source_files(self): + modules = [Extension('foo', ['xxx'], optional=False)] + dist = Distribution({'name': 'xx', 'ext_modules': modules}) + cmd = build_ext(dist) + cmd.ensure_finalized() + self.assertEqual(cmd.get_source_files(), ['xxx']) + + def test_compiler_option(self): + # cmd.compiler is an option and + # should not be overriden by a compiler instance + # when the command is run + dist = Distribution() + cmd = build_ext(dist) + cmd.compiler = 'unix' + cmd.ensure_finalized() + cmd.run() + self.assertEqual(cmd.compiler, 'unix') + + def test_get_outputs(self): + tmp_dir = self.mkdtemp() + c_file = os.path.join(tmp_dir, 'foo.c') + self.write_file(c_file, 'void initfoo(void) {};\n') + ext = Extension('foo', [c_file], optional=False) + dist = Distribution({'name': 'xx', + 'ext_modules': [ext]}) + cmd = build_ext(dist) + cmd.ensure_finalized() + self.assertEqual(len(cmd.get_outputs()), 1) + + if os.name == "nt": + cmd.debug = sys.executable.endswith("_d.exe") + + cmd.build_lib = os.path.join(self.tmp_dir, 'build') + cmd.build_temp = os.path.join(self.tmp_dir, 'tempt') + + # issue #5977 : distutils build_ext.get_outputs + # returns wrong result with --inplace + other_tmp_dir = os.path.realpath(self.mkdtemp()) + old_wd = os.getcwd() + os.chdir(other_tmp_dir) + try: + cmd.inplace = True + cmd.run() + so_file = cmd.get_outputs()[0] + finally: + os.chdir(old_wd) + self.assertTrue(os.path.exists(so_file)) + so_ext = sysconfig.get_config_var('SO') + self.assertTrue(so_file.endswith(so_ext)) + so_dir = os.path.dirname(so_file) + self.assertEqual(so_dir, other_tmp_dir) + + cmd.inplace = False + cmd.run() + so_file = cmd.get_outputs()[0] + self.assertTrue(os.path.exists(so_file)) + self.assertTrue(so_file.endswith(so_ext)) + so_dir = os.path.dirname(so_file) + self.assertEqual(so_dir, cmd.build_lib) + + # inplace = False, cmd.package = 'bar' + build_py = cmd.get_finalized_command('build_py') + build_py.package_dir = 'bar' + path = cmd.get_ext_fullpath('foo') + # checking that the last directory is the build_dir + path = os.path.split(path)[0] + self.assertEqual(path, cmd.build_lib) + + # inplace = True, cmd.package = 'bar' + cmd.inplace = True + other_tmp_dir = os.path.realpath(self.mkdtemp()) + old_wd = os.getcwd() + os.chdir(other_tmp_dir) + try: + path = cmd.get_ext_fullpath('foo') + finally: + os.chdir(old_wd) + # checking that the last directory is bar + path = os.path.split(path)[0] + lastdir = os.path.split(path)[-1] + self.assertEqual(lastdir, 'bar') + + def test_ext_fullpath(self): + ext = sysconfig.get_config_vars()['SO'] + # building lxml.etree inplace + #etree_c = os.path.join(self.tmp_dir, 'lxml.etree.c') + #etree_ext = Extension('lxml.etree', [etree_c]) + #dist = Distribution({'name': 'lxml', 'ext_modules': [etree_ext]}) + dist = Distribution() + cmd = build_ext(dist) + cmd.inplace = True + cmd.distribution.package_dir = 'src' + cmd.distribution.packages = ['lxml', 'lxml.html'] + curdir = os.getcwd() + wanted = os.path.join(curdir, 'src', 'lxml', 'etree' + ext) + path = cmd.get_ext_fullpath('lxml.etree') + self.assertEqual(wanted, path) + + # building lxml.etree not inplace + cmd.inplace = False + cmd.build_lib = os.path.join(curdir, 'tmpdir') + wanted = os.path.join(curdir, 'tmpdir', 'lxml', 'etree' + ext) + path = cmd.get_ext_fullpath('lxml.etree') + self.assertEqual(wanted, path) + + # building twisted.runner.portmap not inplace + build_py = cmd.get_finalized_command('build_py') + build_py.package_dir = None + cmd.distribution.packages = ['twisted', 'twisted.runner.portmap'] + path = cmd.get_ext_fullpath('twisted.runner.portmap') + wanted = os.path.join(curdir, 'tmpdir', 'twisted', 'runner', + 'portmap' + ext) + self.assertEqual(wanted, path) + + # building twisted.runner.portmap inplace + cmd.inplace = True + path = cmd.get_ext_fullpath('twisted.runner.portmap') + wanted = os.path.join(curdir, 'twisted', 'runner', 'portmap' + ext) + self.assertEqual(wanted, path) + + +def test_suite(): + src = _get_source_filename() + if not os.path.exists(src): + if verbose: + print ('test_build_ext: Cannot find source code (test' + ' must run in python build dir)') + return unittest.TestSuite() + else: + return unittest.makeSuite(BuildExtTestCase) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/Lib/packaging/tests/test_command_build_py.py b/Lib/packaging/tests/test_command_build_py.py new file mode 100644 index 0000000..9b40e6d --- /dev/null +++ b/Lib/packaging/tests/test_command_build_py.py @@ -0,0 +1,124 @@ +"""Tests for distutils.command.build_py.""" + +import os +import sys + +from packaging.command.build_py import build_py +from packaging.dist import Distribution +from packaging.errors import PackagingFileError + +from packaging.tests import unittest, support + + +class BuildPyTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + def test_package_data(self): + sources = self.mkdtemp() + pkg_dir = os.path.join(sources, 'pkg') + os.mkdir(pkg_dir) + f = open(os.path.join(pkg_dir, "__init__.py"), "w") + try: + f.write("# Pretend this is a package.") + finally: + f.close() + f = open(os.path.join(pkg_dir, "README.txt"), "w") + try: + f.write("Info about this package") + finally: + f.close() + + destination = self.mkdtemp() + + dist = Distribution({"packages": ["pkg"], + "package_dir": sources}) + # script_name need not exist, it just need to be initialized + + dist.script_name = os.path.join(sources, "setup.py") + dist.command_obj["build"] = support.DummyCommand( + force=False, + build_lib=destination, + use_2to3_fixers=None, + convert_2to3_doctests=None, + use_2to3=False) + dist.packages = ["pkg"] + dist.package_data = {"pkg": ["README.txt"]} + dist.package_dir = sources + + cmd = build_py(dist) + cmd.compile = True + cmd.ensure_finalized() + self.assertEqual(cmd.package_data, dist.package_data) + + cmd.run() + + # This makes sure the list of outputs includes byte-compiled + # files for Python modules but not for package data files + # (there shouldn't *be* byte-code files for those!). + # + self.assertEqual(len(cmd.get_outputs()), 3) + pkgdest = os.path.join(destination, "pkg") + files = os.listdir(pkgdest) + self.assertIn("__init__.py", files) + self.assertIn("__init__.pyc", files) + self.assertIn("README.txt", files) + + def test_empty_package_dir(self): + # See SF 1668596/1720897. + cwd = os.getcwd() + + # create the distribution files. + sources = self.mkdtemp() + pkg = os.path.join(sources, 'pkg') + os.mkdir(pkg) + open(os.path.join(pkg, "__init__.py"), "w").close() + testdir = os.path.join(pkg, "doc") + os.mkdir(testdir) + open(os.path.join(testdir, "testfile"), "w").close() + + os.chdir(sources) + old_stdout = sys.stdout + #sys.stdout = StringIO.StringIO() + + try: + dist = Distribution({"packages": ["pkg"], + "package_dir": sources, + "package_data": {"pkg": ["doc/*"]}}) + # script_name need not exist, it just need to be initialized + dist.script_name = os.path.join(sources, "setup.py") + dist.script_args = ["build"] + dist.parse_command_line() + + try: + dist.run_commands() + except PackagingFileError as e: + self.fail("failed package_data test when package_dir is ''") + finally: + # Restore state. + os.chdir(cwd) + sys.stdout = old_stdout + + @unittest.skipUnless(hasattr(sys, 'dont_write_bytecode'), + 'sys.dont_write_bytecode not supported') + def test_dont_write_bytecode(self): + # makes sure byte_compile is not used + pkg_dir, dist = self.create_dist() + cmd = build_py(dist) + cmd.compile = True + cmd.optimize = 1 + + old_dont_write_bytecode = sys.dont_write_bytecode + sys.dont_write_bytecode = True + try: + cmd.byte_compile([]) + finally: + sys.dont_write_bytecode = old_dont_write_bytecode + + self.assertIn('byte-compiling is disabled', self.get_logs()[0]) + +def test_suite(): + return unittest.makeSuite(BuildPyTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_build_scripts.py b/Lib/packaging/tests/test_command_build_scripts.py new file mode 100644 index 0000000..60d8b68 --- /dev/null +++ b/Lib/packaging/tests/test_command_build_scripts.py @@ -0,0 +1,112 @@ +"""Tests for distutils.command.build_scripts.""" + +import os +import sys +import sysconfig +from packaging.dist import Distribution +from packaging.command.build_scripts import build_scripts + +from packaging.tests import unittest, support + + +class BuildScriptsTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + def test_default_settings(self): + cmd = self.get_build_scripts_cmd("/foo/bar", []) + self.assertFalse(cmd.force) + self.assertIs(cmd.build_dir, None) + + cmd.finalize_options() + + self.assertTrue(cmd.force) + self.assertEqual(cmd.build_dir, "/foo/bar") + + def test_build(self): + source = self.mkdtemp() + target = self.mkdtemp() + expected = self.write_sample_scripts(source) + + cmd = self.get_build_scripts_cmd(target, + [os.path.join(source, fn) + for fn in expected]) + cmd.finalize_options() + cmd.run() + + built = os.listdir(target) + for name in expected: + self.assertIn(name, built) + + def get_build_scripts_cmd(self, target, scripts): + dist = Distribution() + dist.scripts = scripts + dist.command_obj["build"] = support.DummyCommand( + build_scripts=target, + force=True, + executable=sys.executable, + use_2to3=False, + use_2to3_fixers=None, + convert_2to3_doctests=None + ) + return build_scripts(dist) + + def write_sample_scripts(self, dir): + expected = [] + expected.append("script1.py") + self.write_script(dir, "script1.py", + ("#! /usr/bin/env python2.3\n" + "# bogus script w/ Python sh-bang\n" + "pass\n")) + expected.append("script2.py") + self.write_script(dir, "script2.py", + ("#!/usr/bin/python\n" + "# bogus script w/ Python sh-bang\n" + "pass\n")) + expected.append("shell.sh") + self.write_script(dir, "shell.sh", + ("#!/bin/sh\n" + "# bogus shell script w/ sh-bang\n" + "exit 0\n")) + return expected + + def write_script(self, dir, name, text): + f = open(os.path.join(dir, name), "w") + try: + f.write(text) + finally: + f.close() + + def test_version_int(self): + source = self.mkdtemp() + target = self.mkdtemp() + expected = self.write_sample_scripts(source) + + + cmd = self.get_build_scripts_cmd(target, + [os.path.join(source, fn) + for fn in expected]) + cmd.finalize_options() + + # http://bugs.python.org/issue4524 + # + # On linux-g++-32 with command line `./configure --enable-ipv6 + # --with-suffix=3`, python is compiled okay but the build scripts + # failed when writing the name of the executable + old = sysconfig.get_config_vars().get('VERSION') + sysconfig._CONFIG_VARS['VERSION'] = 4 + try: + cmd.run() + finally: + if old is not None: + sysconfig._CONFIG_VARS['VERSION'] = old + + built = os.listdir(target) + for name in expected: + self.assertIn(name, built) + +def test_suite(): + return unittest.makeSuite(BuildScriptsTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_check.py b/Lib/packaging/tests/test_command_check.py new file mode 100644 index 0000000..8b32673 --- /dev/null +++ b/Lib/packaging/tests/test_command_check.py @@ -0,0 +1,131 @@ +"""Tests for distutils.command.check.""" + +import logging +from packaging.command.check import check +from packaging.metadata import _HAS_DOCUTILS +from packaging.errors import PackagingSetupError, MetadataMissingError +from packaging.tests import unittest, support + + +class CheckTestCase(support.LoggingCatcher, + support.TempdirManager, + unittest.TestCase): + + def _run(self, metadata=None, **options): + if metadata is None: + metadata = {'name': 'xxx', 'version': '1.2'} + pkg_info, dist = self.create_dist(**metadata) + cmd = check(dist) + cmd.initialize_options() + for name, value in options.items(): + setattr(cmd, name, value) + cmd.ensure_finalized() + cmd.run() + return cmd + + def test_check_metadata(self): + # let's run the command with no metadata at all + # by default, check is checking the metadata + # should have some warnings + cmd = self._run() + # trick: using assertNotEqual with an empty list will give us a more + # useful error message than assertGreater(.., 0) when the code change + # and the test fails + self.assertNotEqual([], self.get_logs(logging.WARNING)) + + # now let's add the required fields + # and run it again, to make sure we don't get + # any warning anymore + self.loghandler.flush() + metadata = {'home_page': 'xxx', 'author': 'xxx', + 'author_email': 'xxx', + 'name': 'xxx', 'version': '4.2', + } + cmd = self._run(metadata) + self.assertEqual([], self.get_logs(logging.WARNING)) + + # now with the strict mode, we should + # get an error if there are missing metadata + self.assertRaises(MetadataMissingError, self._run, {}, **{'strict': 1}) + self.assertRaises(PackagingSetupError, self._run, + {'name': 'xxx', 'version': 'xxx'}, **{'strict': 1}) + + # and of course, no error when all metadata fields are present + self.loghandler.flush() + cmd = self._run(metadata, strict=True) + self.assertEqual([], self.get_logs(logging.WARNING)) + + def test_check_metadata_1_2(self): + # let's run the command with no metadata at all + # by default, check is checking the metadata + # should have some warnings + cmd = self._run() + self.assertNotEqual([], self.get_logs(logging.WARNING)) + + # now let's add the required fields and run it again, to make sure we + # don't get any warning anymore let's use requires_python as a marker + # to enforce Metadata-Version 1.2 + metadata = {'home_page': 'xxx', 'author': 'xxx', + 'author_email': 'xxx', + 'name': 'xxx', 'version': '4.2', + 'requires_python': '2.4', + } + self.loghandler.flush() + cmd = self._run(metadata) + self.assertEqual([], self.get_logs(logging.WARNING)) + + # now with the strict mode, we should + # get an error if there are missing metadata + self.assertRaises(MetadataMissingError, self._run, {}, **{'strict': 1}) + self.assertRaises(PackagingSetupError, self._run, + {'name': 'xxx', 'version': 'xxx'}, **{'strict': 1}) + + # complain about version format + metadata['version'] = 'xxx' + self.assertRaises(PackagingSetupError, self._run, metadata, + **{'strict': 1}) + + # now with correct version format again + metadata['version'] = '4.2' + self.loghandler.flush() + cmd = self._run(metadata, strict=True) + self.assertEqual([], self.get_logs(logging.WARNING)) + + @unittest.skipUnless(_HAS_DOCUTILS, "requires docutils") + def test_check_restructuredtext(self): + # let's see if it detects broken rest in long_description + broken_rest = 'title\n===\n\ntest' + pkg_info, dist = self.create_dist(description=broken_rest) + cmd = check(dist) + cmd.check_restructuredtext() + self.assertEqual(len(self.get_logs(logging.WARNING)), 1) + + self.loghandler.flush() + pkg_info, dist = self.create_dist(description='title\n=====\n\ntest') + cmd = check(dist) + cmd.check_restructuredtext() + self.assertEqual([], self.get_logs(logging.WARNING)) + + def test_check_all(self): + self.assertRaises(PackagingSetupError, self._run, + {'name': 'xxx', 'version': 'xxx'}, **{'strict': 1, + 'all': 1}) + self.assertRaises(MetadataMissingError, self._run, + {}, **{'strict': 1, + 'all': 1}) + + def test_check_hooks(self): + pkg_info, dist = self.create_dist() + dist.command_options['install_dist'] = { + 'pre_hook': ('file', {"a": 'some.nonextistant.hook.ghrrraarrhll'}), + } + cmd = check(dist) + cmd.check_hooks_resolvable() + self.assertEqual(len(self.get_logs(logging.WARNING)), 1) + + +def test_suite(): + return unittest.makeSuite(CheckTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_clean.py b/Lib/packaging/tests/test_command_clean.py new file mode 100644 index 0000000..8d29e4d --- /dev/null +++ b/Lib/packaging/tests/test_command_clean.py @@ -0,0 +1,48 @@ +"""Tests for distutils.command.clean.""" +import os + +from packaging.command.clean import clean +from packaging.tests import unittest, support + + +class cleanTestCase(support.TempdirManager, support.LoggingCatcher, + unittest.TestCase): + + def test_simple_run(self): + pkg_dir, dist = self.create_dist() + cmd = clean(dist) + + # let's add some elements clean should remove + dirs = [(d, os.path.join(pkg_dir, d)) + for d in ('build_temp', 'build_lib', 'bdist_base', + 'build_scripts', 'build_base')] + + for name, path in dirs: + os.mkdir(path) + setattr(cmd, name, path) + if name == 'build_base': + continue + for f in ('one', 'two', 'three'): + self.write_file(os.path.join(path, f)) + + # let's run the command + cmd.all = True + cmd.ensure_finalized() + cmd.run() + + # make sure the files where removed + for name, path in dirs: + self.assertFalse(os.path.exists(path), + '%r was not removed' % path) + + # let's run the command again (should spit warnings but succeed) + cmd.all = True + cmd.ensure_finalized() + cmd.run() + + +def test_suite(): + return unittest.makeSuite(cleanTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_cmd.py b/Lib/packaging/tests/test_command_cmd.py new file mode 100644 index 0000000..8ac9dce --- /dev/null +++ b/Lib/packaging/tests/test_command_cmd.py @@ -0,0 +1,101 @@ +"""Tests for distutils.cmd.""" +import os + +from packaging.command.cmd import Command +from packaging.dist import Distribution +from packaging.errors import PackagingOptionError +from packaging.tests import support, unittest + + +class MyCmd(Command): + def initialize_options(self): + pass + + +class CommandTestCase(support.LoggingCatcher, + unittest.TestCase): + + def setUp(self): + super(CommandTestCase, self).setUp() + dist = Distribution() + self.cmd = MyCmd(dist) + + def test_make_file(self): + cmd = self.cmd + + # making sure it raises when infiles is not a string or a list/tuple + self.assertRaises(TypeError, cmd.make_file, + infiles=1, outfile='', func='func', args=()) + + # making sure execute gets called properly + def _execute(func, args, exec_msg, level): + self.assertEqual(exec_msg, 'generating out from in') + cmd.force = True + cmd.execute = _execute + cmd.make_file(infiles='in', outfile='out', func='func', args=()) + + def test_dump_options(self): + cmd = self.cmd + cmd.option1 = 1 + cmd.option2 = 1 + cmd.user_options = [('option1', '', ''), ('option2', '', '')] + cmd.dump_options() + + wanted = ["command options for 'MyCmd':", ' option1 = 1', + ' option2 = 1'] + msgs = self.get_logs() + self.assertEqual(msgs, wanted) + + def test_ensure_string(self): + cmd = self.cmd + cmd.option1 = 'ok' + cmd.ensure_string('option1') + + cmd.option2 = None + cmd.ensure_string('option2', 'xxx') + self.assertTrue(hasattr(cmd, 'option2')) + + cmd.option3 = 1 + self.assertRaises(PackagingOptionError, cmd.ensure_string, 'option3') + + def test_ensure_string_list(self): + cmd = self.cmd + cmd.option1 = 'ok,dok' + cmd.ensure_string_list('option1') + self.assertEqual(cmd.option1, ['ok', 'dok']) + + cmd.yes_string_list = ['one', 'two', 'three'] + cmd.yes_string_list2 = 'ok' + cmd.ensure_string_list('yes_string_list') + cmd.ensure_string_list('yes_string_list2') + self.assertEqual(cmd.yes_string_list, ['one', 'two', 'three']) + self.assertEqual(cmd.yes_string_list2, ['ok']) + + cmd.not_string_list = ['one', 2, 'three'] + cmd.not_string_list2 = object() + self.assertRaises(PackagingOptionError, + cmd.ensure_string_list, 'not_string_list') + + self.assertRaises(PackagingOptionError, + cmd.ensure_string_list, 'not_string_list2') + + def test_ensure_filename(self): + cmd = self.cmd + cmd.option1 = __file__ + cmd.ensure_filename('option1') + cmd.option2 = 'xxx' + self.assertRaises(PackagingOptionError, cmd.ensure_filename, 'option2') + + def test_ensure_dirname(self): + cmd = self.cmd + cmd.option1 = os.path.dirname(__file__) or os.curdir + cmd.ensure_dirname('option1') + cmd.option2 = 'xxx' + self.assertRaises(PackagingOptionError, cmd.ensure_dirname, 'option2') + + +def test_suite(): + return unittest.makeSuite(CommandTestCase) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/Lib/packaging/tests/test_command_config.py b/Lib/packaging/tests/test_command_config.py new file mode 100644 index 0000000..6d780c5 --- /dev/null +++ b/Lib/packaging/tests/test_command_config.py @@ -0,0 +1,76 @@ +"""Tests for distutils.command.config.""" +import os +import sys +import logging + +from packaging.command.config import dump_file, config +from packaging.tests import unittest, support + + +class ConfigTestCase(support.LoggingCatcher, + support.TempdirManager, + unittest.TestCase): + + def test_dump_file(self): + this_file = __file__.rstrip('co') + with open(this_file) as f: + numlines = len(f.readlines()) + + dump_file(this_file, 'I am the header') + + logs = [] + for log in self.get_logs(logging.INFO): + logs.extend(line for line in log.split('\n')) + self.assertEqual(len(logs), numlines + 2) + + @unittest.skipIf(sys.platform == 'win32', 'disabled on win32') + def test_search_cpp(self): + pkg_dir, dist = self.create_dist() + cmd = config(dist) + + # simple pattern searches + match = cmd.search_cpp(pattern='xxx', body='// xxx') + self.assertEqual(match, 0) + + match = cmd.search_cpp(pattern='_configtest', body='// xxx') + self.assertEqual(match, 1) + + def test_finalize_options(self): + # finalize_options does a bit of transformation + # on options + pkg_dir, dist = self.create_dist() + cmd = config(dist) + cmd.include_dirs = 'one%stwo' % os.pathsep + cmd.libraries = 'one' + cmd.library_dirs = 'three%sfour' % os.pathsep + cmd.ensure_finalized() + + self.assertEqual(cmd.include_dirs, ['one', 'two']) + self.assertEqual(cmd.libraries, ['one']) + self.assertEqual(cmd.library_dirs, ['three', 'four']) + + def test_clean(self): + # _clean removes files + tmp_dir = self.mkdtemp() + f1 = os.path.join(tmp_dir, 'one') + f2 = os.path.join(tmp_dir, 'two') + + self.write_file(f1, 'xxx') + self.write_file(f2, 'xxx') + + for f in (f1, f2): + self.assertTrue(os.path.exists(f)) + + pkg_dir, dist = self.create_dist() + cmd = config(dist) + cmd._clean(f1, f2) + + for f in (f1, f2): + self.assertFalse(os.path.exists(f)) + + +def test_suite(): + return unittest.makeSuite(ConfigTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_install_data.py b/Lib/packaging/tests/test_command_install_data.py new file mode 100644 index 0000000..8b8bbac --- /dev/null +++ b/Lib/packaging/tests/test_command_install_data.py @@ -0,0 +1,80 @@ +"""Tests for packaging.command.install_data.""" +import os +import sysconfig +from sysconfig import _get_default_scheme +from packaging.tests import unittest, support +from packaging.command.install_data import install_data + + +class InstallDataTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + def test_simple_run(self): + self.addCleanup(setattr, sysconfig, '_SCHEMES', sysconfig._SCHEMES) + + pkg_dir, dist = self.create_dist() + cmd = install_data(dist) + cmd.install_dir = inst = os.path.join(pkg_dir, 'inst') + + sysconfig._SCHEMES.set(_get_default_scheme(), 'inst', + os.path.join(pkg_dir, 'inst')) + sysconfig._SCHEMES.set(_get_default_scheme(), 'inst2', + os.path.join(pkg_dir, 'inst2')) + + one = os.path.join(pkg_dir, 'one') + self.write_file(one, 'xxx') + inst2 = os.path.join(pkg_dir, 'inst2') + two = os.path.join(pkg_dir, 'two') + self.write_file(two, 'xxx') + + cmd.data_files = {one: '{inst}/one', two: '{inst2}/two'} + self.assertCountEqual(cmd.get_inputs(), [one, two]) + + # let's run the command + cmd.ensure_finalized() + cmd.run() + + # let's check the result + self.assertEqual(len(cmd.get_outputs()), 2) + rtwo = os.path.split(two)[-1] + self.assertTrue(os.path.exists(os.path.join(inst2, rtwo))) + rone = os.path.split(one)[-1] + self.assertTrue(os.path.exists(os.path.join(inst, rone))) + cmd.outfiles = [] + + # let's try with warn_dir one + cmd.warn_dir = True + cmd.ensure_finalized() + cmd.run() + + # let's check the result + self.assertEqual(len(cmd.get_outputs()), 2) + self.assertTrue(os.path.exists(os.path.join(inst2, rtwo))) + self.assertTrue(os.path.exists(os.path.join(inst, rone))) + cmd.outfiles = [] + + # now using root and empty dir + cmd.root = os.path.join(pkg_dir, 'root') + three = os.path.join(cmd.install_dir, 'three') + self.write_file(three, 'xx') + + sysconfig._SCHEMES.set(_get_default_scheme(), 'inst3', + cmd.install_dir) + + cmd.data_files = {one: '{inst}/one', two: '{inst2}/two', + three: '{inst3}/three'} + cmd.ensure_finalized() + cmd.run() + + # let's check the result + self.assertEqual(len(cmd.get_outputs()), 3) + self.assertTrue(os.path.exists(os.path.join(inst2, rtwo))) + self.assertTrue(os.path.exists(os.path.join(inst, rone))) + + +def test_suite(): + return unittest.makeSuite(InstallDataTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_install_dist.py b/Lib/packaging/tests/test_command_install_dist.py new file mode 100644 index 0000000..a06d1f6 --- /dev/null +++ b/Lib/packaging/tests/test_command_install_dist.py @@ -0,0 +1,210 @@ +"""Tests for packaging.command.install.""" + +import os +import sys + +from sysconfig import (get_scheme_names, get_config_vars, + _SCHEMES, get_config_var, get_path) + +_CONFIG_VARS = get_config_vars() + +from packaging.tests import captured_stdout + +from packaging.command.install_dist import install_dist +from packaging.command import install_dist as install_module +from packaging.dist import Distribution +from packaging.errors import PackagingOptionError + +from packaging.tests import unittest, support + + +class InstallTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + def test_home_installation_scheme(self): + # This ensure two things: + # - that --home generates the desired set of directory names + # - test --home is supported on all platforms + builddir = self.mkdtemp() + destination = os.path.join(builddir, "installation") + + dist = Distribution({"name": "foopkg"}) + # script_name need not exist, it just need to be initialized + dist.script_name = os.path.join(builddir, "setup.py") + dist.command_obj["build"] = support.DummyCommand( + build_base=builddir, + build_lib=os.path.join(builddir, "lib"), + ) + + old_posix_prefix = _SCHEMES.get('posix_prefix', 'platinclude') + old_posix_home = _SCHEMES.get('posix_home', 'platinclude') + + new_path = '{platbase}/include/python{py_version_short}' + _SCHEMES.set('posix_prefix', 'platinclude', new_path) + _SCHEMES.set('posix_home', 'platinclude', '{platbase}/include/python') + + try: + cmd = install_dist(dist) + cmd.home = destination + cmd.ensure_finalized() + finally: + _SCHEMES.set('posix_prefix', 'platinclude', old_posix_prefix) + _SCHEMES.set('posix_home', 'platinclude', old_posix_home) + + self.assertEqual(cmd.install_base, destination) + self.assertEqual(cmd.install_platbase, destination) + + def check_path(got, expected): + got = os.path.normpath(got) + expected = os.path.normpath(expected) + self.assertEqual(got, expected) + + libdir = os.path.join(destination, "lib", "python") + check_path(cmd.install_lib, libdir) + check_path(cmd.install_platlib, libdir) + check_path(cmd.install_purelib, libdir) + check_path(cmd.install_headers, + os.path.join(destination, "include", "python", "foopkg")) + check_path(cmd.install_scripts, os.path.join(destination, "bin")) + check_path(cmd.install_data, destination) + + @unittest.skipIf(sys.version < '2.6', 'requires Python 2.6 or higher') + def test_user_site(self): + # test install with --user + # preparing the environment for the test + self.old_user_base = get_config_var('userbase') + self.old_user_site = get_path('purelib', '%s_user' % os.name) + self.tmpdir = self.mkdtemp() + self.user_base = os.path.join(self.tmpdir, 'B') + self.user_site = os.path.join(self.tmpdir, 'S') + _CONFIG_VARS['userbase'] = self.user_base + scheme = '%s_user' % os.name + _SCHEMES.set(scheme, 'purelib', self.user_site) + + def _expanduser(path): + if path[0] == '~': + path = os.path.normpath(self.tmpdir) + path[1:] + return path + + self.old_expand = os.path.expanduser + os.path.expanduser = _expanduser + + try: + # this is the actual test + self._test_user_site() + finally: + _CONFIG_VARS['userbase'] = self.old_user_base + _SCHEMES.set(scheme, 'purelib', self.old_user_site) + os.path.expanduser = self.old_expand + + def _test_user_site(self): + schemes = get_scheme_names() + for key in ('nt_user', 'posix_user', 'os2_home'): + self.assertIn(key, schemes) + + dist = Distribution({'name': 'xx'}) + cmd = install_dist(dist) + # making sure the user option is there + options = [name for name, short, lable in + cmd.user_options] + self.assertIn('user', options) + + # setting a value + cmd.user = True + + # user base and site shouldn't be created yet + self.assertFalse(os.path.exists(self.user_base)) + self.assertFalse(os.path.exists(self.user_site)) + + # let's run finalize + cmd.ensure_finalized() + + # now they should + self.assertTrue(os.path.exists(self.user_base)) + self.assertTrue(os.path.exists(self.user_site)) + + self.assertIn('userbase', cmd.config_vars) + self.assertIn('usersite', cmd.config_vars) + + def test_handle_extra_path(self): + dist = Distribution({'name': 'xx', 'extra_path': 'path,dirs'}) + cmd = install_dist(dist) + + # two elements + cmd.handle_extra_path() + self.assertEqual(cmd.extra_path, ['path', 'dirs']) + self.assertEqual(cmd.extra_dirs, 'dirs') + self.assertEqual(cmd.path_file, 'path') + + # one element + cmd.extra_path = ['path'] + cmd.handle_extra_path() + self.assertEqual(cmd.extra_path, ['path']) + self.assertEqual(cmd.extra_dirs, 'path') + self.assertEqual(cmd.path_file, 'path') + + # none + dist.extra_path = cmd.extra_path = None + cmd.handle_extra_path() + self.assertEqual(cmd.extra_path, None) + self.assertEqual(cmd.extra_dirs, '') + self.assertEqual(cmd.path_file, None) + + # three elements (no way !) + cmd.extra_path = 'path,dirs,again' + self.assertRaises(PackagingOptionError, cmd.handle_extra_path) + + def test_finalize_options(self): + dist = Distribution({'name': 'xx'}) + cmd = install_dist(dist) + + # must supply either prefix/exec-prefix/home or + # install-base/install-platbase -- not both + cmd.prefix = 'prefix' + cmd.install_base = 'base' + self.assertRaises(PackagingOptionError, cmd.finalize_options) + + # must supply either home or prefix/exec-prefix -- not both + cmd.install_base = None + cmd.home = 'home' + self.assertRaises(PackagingOptionError, cmd.finalize_options) + + if sys.version >= '2.6': + # can't combine user with with prefix/exec_prefix/home or + # install_(plat)base + cmd.prefix = None + cmd.user = 'user' + self.assertRaises(PackagingOptionError, cmd.finalize_options) + + def test_old_record(self): + # test pre-PEP 376 --record option (outside dist-info dir) + install_dir = self.mkdtemp() + pkgdir, dist = self.create_dist() + + dist = Distribution() + cmd = install_dist(dist) + dist.command_obj['install_dist'] = cmd + cmd.root = install_dir + cmd.record = os.path.join(pkgdir, 'filelist') + cmd.ensure_finalized() + cmd.run() + + # let's check the record file was created with four + # lines, one for each .dist-info entry: METADATA, + # INSTALLER, REQUSTED, RECORD + f = open(cmd.record) + try: + self.assertEqual(len(f.readlines()), 4) + finally: + f.close() + + # XXX test that fancy_getopt is okay with options named + # record and no-record but unrelated + + +def test_suite(): + return unittest.makeSuite(InstallTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_install_distinfo.py b/Lib/packaging/tests/test_command_install_distinfo.py new file mode 100644 index 0000000..3d33691 --- /dev/null +++ b/Lib/packaging/tests/test_command_install_distinfo.py @@ -0,0 +1,192 @@ +"""Tests for ``packaging.command.install_distinfo``. """ + +import os +import csv +import hashlib +import sys + +from packaging.command.install_distinfo import install_distinfo +from packaging.command.cmd import Command +from packaging.metadata import Metadata +from packaging.tests import unittest, support + + +class DummyInstallCmd(Command): + + def __init__(self, dist=None): + self.outputs = [] + self.distribution = dist + + def __getattr__(self, name): + return None + + def ensure_finalized(self): + pass + + def get_outputs(self): + return (self.outputs + + self.get_finalized_command('install_distinfo').get_outputs()) + + +class InstallDistinfoTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + checkLists = lambda self, x, y: self.assertListEqual(sorted(x), sorted(y)) + + def test_empty_install(self): + pkg_dir, dist = self.create_dist(name='foo', + version='1.0') + install_dir = self.mkdtemp() + + install = DummyInstallCmd(dist) + dist.command_obj['install_dist'] = install + + cmd = install_distinfo(dist) + dist.command_obj['install_distinfo'] = cmd + + cmd.initialize_options() + cmd.distinfo_dir = install_dir + cmd.ensure_finalized() + cmd.run() + + self.checkLists(os.listdir(install_dir), ['foo-1.0.dist-info']) + + dist_info = os.path.join(install_dir, 'foo-1.0.dist-info') + self.checkLists(os.listdir(dist_info), + ['METADATA', 'RECORD', 'REQUESTED', 'INSTALLER']) + with open(os.path.join(dist_info, 'INSTALLER')) as fp: + self.assertEqual(fp.read(), 'distutils') + with open(os.path.join(dist_info, 'REQUESTED')) as fp: + self.assertEqual(fp.read(), '') + meta_path = os.path.join(dist_info, 'METADATA') + self.assertTrue(Metadata(path=meta_path).check()) + + def test_installer(self): + pkg_dir, dist = self.create_dist(name='foo', + version='1.0') + install_dir = self.mkdtemp() + + install = DummyInstallCmd(dist) + dist.command_obj['install_dist'] = install + + cmd = install_distinfo(dist) + dist.command_obj['install_distinfo'] = cmd + + cmd.initialize_options() + cmd.distinfo_dir = install_dir + cmd.installer = 'bacon-python' + cmd.ensure_finalized() + cmd.run() + + dist_info = os.path.join(install_dir, 'foo-1.0.dist-info') + with open(os.path.join(dist_info, 'INSTALLER')) as fp: + self.assertEqual(fp.read(), 'bacon-python') + + def test_requested(self): + pkg_dir, dist = self.create_dist(name='foo', + version='1.0') + install_dir = self.mkdtemp() + + install = DummyInstallCmd(dist) + dist.command_obj['install_dist'] = install + + cmd = install_distinfo(dist) + dist.command_obj['install_distinfo'] = cmd + + cmd.initialize_options() + cmd.distinfo_dir = install_dir + cmd.requested = False + cmd.ensure_finalized() + cmd.run() + + dist_info = os.path.join(install_dir, 'foo-1.0.dist-info') + self.checkLists(os.listdir(dist_info), + ['METADATA', 'RECORD', 'INSTALLER']) + + def test_no_record(self): + pkg_dir, dist = self.create_dist(name='foo', + version='1.0') + install_dir = self.mkdtemp() + + install = DummyInstallCmd(dist) + dist.command_obj['install_dist'] = install + + cmd = install_distinfo(dist) + dist.command_obj['install_distinfo'] = cmd + + cmd.initialize_options() + cmd.distinfo_dir = install_dir + cmd.no_record = True + cmd.ensure_finalized() + cmd.run() + + dist_info = os.path.join(install_dir, 'foo-1.0.dist-info') + self.checkLists(os.listdir(dist_info), + ['METADATA', 'REQUESTED', 'INSTALLER']) + + def test_record(self): + pkg_dir, dist = self.create_dist(name='foo', + version='1.0') + install_dir = self.mkdtemp() + + install = DummyInstallCmd(dist) + dist.command_obj['install_dist'] = install + + fake_dists = os.path.join(os.path.dirname(__file__), 'fake_dists') + fake_dists = os.path.realpath(fake_dists) + + # for testing, we simply add all files from _backport's fake_dists + dirs = [] + for dir in os.listdir(fake_dists): + full_path = os.path.join(fake_dists, dir) + if (not dir.endswith('.egg') or dir.endswith('.egg-info') or + dir.endswith('.dist-info')) and os.path.isdir(full_path): + dirs.append(full_path) + + for dir in dirs: + for path, subdirs, files in os.walk(dir): + install.outputs += [os.path.join(path, f) for f in files] + install.outputs += [os.path.join('path', f + 'c') + for f in files if f.endswith('.py')] + + cmd = install_distinfo(dist) + dist.command_obj['install_distinfo'] = cmd + + cmd.initialize_options() + cmd.distinfo_dir = install_dir + cmd.ensure_finalized() + cmd.run() + + dist_info = os.path.join(install_dir, 'foo-1.0.dist-info') + + expected = [] + for f in install.get_outputs(): + if (f.endswith('.pyc') or f == os.path.join( + install_dir, 'foo-1.0.dist-info', 'RECORD')): + expected.append([f, '', '']) + else: + size = os.path.getsize(f) + md5 = hashlib.md5() + with open(f) as fp: + md5.update(fp.read().encode()) + hash = md5.hexdigest() + expected.append([f, hash, str(size)]) + + parsed = [] + with open(os.path.join(dist_info, 'RECORD'), 'r') as f: + reader = csv.reader(f, delimiter=',', + lineterminator=os.linesep, + quotechar='"') + parsed = list(reader) + + self.maxDiff = None + self.checkLists(parsed, expected) + + +def test_suite(): + return unittest.makeSuite(InstallDistinfoTestCase) + + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_install_headers.py b/Lib/packaging/tests/test_command_install_headers.py new file mode 100644 index 0000000..f2906a7 --- /dev/null +++ b/Lib/packaging/tests/test_command_install_headers.py @@ -0,0 +1,38 @@ +"""Tests for packaging.command.install_headers.""" +import os + +from packaging.command.install_headers import install_headers +from packaging.tests import unittest, support + + +class InstallHeadersTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + def test_simple_run(self): + # we have two headers + header_list = self.mkdtemp() + header1 = os.path.join(header_list, 'header1') + header2 = os.path.join(header_list, 'header2') + self.write_file(header1) + self.write_file(header2) + headers = [header1, header2] + + pkg_dir, dist = self.create_dist(headers=headers) + cmd = install_headers(dist) + self.assertEqual(cmd.get_inputs(), headers) + + # let's run the command + cmd.install_dir = os.path.join(pkg_dir, 'inst') + cmd.ensure_finalized() + cmd.run() + + # let's check the results + self.assertEqual(len(cmd.get_outputs()), 2) + + +def test_suite(): + return unittest.makeSuite(InstallHeadersTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_install_lib.py b/Lib/packaging/tests/test_command_install_lib.py new file mode 100644 index 0000000..99d47dd --- /dev/null +++ b/Lib/packaging/tests/test_command_install_lib.py @@ -0,0 +1,111 @@ +"""Tests for packaging.command.install_data.""" +import sys +import os + +from packaging.tests import unittest, support +from packaging.command.install_lib import install_lib +from packaging.compiler.extension import Extension +from packaging.errors import PackagingOptionError + +try: + no_bytecode = sys.dont_write_bytecode + bytecode_support = True +except AttributeError: + no_bytecode = False + bytecode_support = False + + +class InstallLibTestCase(support.TempdirManager, + support.LoggingCatcher, + support.EnvironRestorer, + unittest.TestCase): + + restore_environ = ['PYTHONPATH'] + + def test_finalize_options(self): + pkg_dir, dist = self.create_dist() + cmd = install_lib(dist) + + cmd.finalize_options() + self.assertTrue(cmd.compile) + self.assertEqual(cmd.optimize, 0) + + # optimize must be 0, 1, or 2 + cmd.optimize = 'foo' + self.assertRaises(PackagingOptionError, cmd.finalize_options) + cmd.optimize = '4' + self.assertRaises(PackagingOptionError, cmd.finalize_options) + + cmd.optimize = '2' + cmd.finalize_options() + self.assertEqual(cmd.optimize, 2) + + @unittest.skipIf(no_bytecode, 'byte-compile not supported') + def test_byte_compile(self): + pkg_dir, dist = self.create_dist() + cmd = install_lib(dist) + cmd.compile = True + cmd.optimize = 1 + + f = os.path.join(pkg_dir, 'foo.py') + self.write_file(f, '# python file') + cmd.byte_compile([f]) + self.assertTrue(os.path.exists(os.path.join(pkg_dir, 'foo.pyc'))) + self.assertTrue(os.path.exists(os.path.join(pkg_dir, 'foo.pyo'))) + + def test_get_outputs(self): + pkg_dir, dist = self.create_dist() + cmd = install_lib(dist) + + # setting up a dist environment + cmd.compile = True + cmd.optimize = 1 + cmd.install_dir = pkg_dir + f = os.path.join(pkg_dir, '__init__.py') + self.write_file(f, '# python package') + cmd.distribution.ext_modules = [Extension('foo', ['xxx'])] + cmd.distribution.packages = [pkg_dir] + cmd.distribution.script_name = 'setup.py' + + # get_output should return 4 elements + self.assertEqual(len(cmd.get_outputs()), 4) + + def test_get_inputs(self): + pkg_dir, dist = self.create_dist() + cmd = install_lib(dist) + + # setting up a dist environment + cmd.compile = True + cmd.optimize = 1 + cmd.install_dir = pkg_dir + f = os.path.join(pkg_dir, '__init__.py') + self.write_file(f, '# python package') + cmd.distribution.ext_modules = [Extension('foo', ['xxx'])] + cmd.distribution.packages = [pkg_dir] + cmd.distribution.script_name = 'setup.py' + + # get_input should return 2 elements + self.assertEqual(len(cmd.get_inputs()), 2) + + @unittest.skipUnless(bytecode_support, + 'sys.dont_write_bytecode not supported') + def test_dont_write_bytecode(self): + # makes sure byte_compile is not used + pkg_dir, dist = self.create_dist() + cmd = install_lib(dist) + cmd.compile = True + cmd.optimize = 1 + + self.addCleanup(setattr, sys, 'dont_write_bytecode', + sys.dont_write_bytecode) + sys.dont_write_bytecode = True + cmd.byte_compile([]) + + self.assertIn('byte-compiling is disabled', self.get_logs()[0]) + + +def test_suite(): + return unittest.makeSuite(InstallLibTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_install_scripts.py b/Lib/packaging/tests/test_command_install_scripts.py new file mode 100644 index 0000000..08c7338 --- /dev/null +++ b/Lib/packaging/tests/test_command_install_scripts.py @@ -0,0 +1,78 @@ +"""Tests for packaging.command.install_scripts.""" +import os + +from packaging.tests import unittest, support +from packaging.command.install_scripts import install_scripts +from packaging.dist import Distribution + + +class InstallScriptsTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + def test_default_settings(self): + dist = Distribution() + dist.command_obj["build"] = support.DummyCommand( + build_scripts="/foo/bar") + dist.command_obj["install_dist"] = support.DummyCommand( + install_scripts="/splat/funk", + force=True, + skip_build=True, + ) + cmd = install_scripts(dist) + self.assertFalse(cmd.force) + self.assertFalse(cmd.skip_build) + self.assertIs(cmd.build_dir, None) + self.assertIs(cmd.install_dir, None) + + cmd.finalize_options() + + self.assertTrue(cmd.force) + self.assertTrue(cmd.skip_build) + self.assertEqual(cmd.build_dir, "/foo/bar") + self.assertEqual(cmd.install_dir, "/splat/funk") + + def test_installation(self): + source = self.mkdtemp() + expected = [] + + def write_script(name, text): + expected.append(name) + f = open(os.path.join(source, name), "w") + try: + f.write(text) + finally: + f.close() + + write_script("script1.py", ("#! /usr/bin/env python2.3\n" + "# bogus script w/ Python sh-bang\n" + "pass\n")) + write_script("script2.py", ("#!/usr/bin/python\n" + "# bogus script w/ Python sh-bang\n" + "pass\n")) + write_script("shell.sh", ("#!/bin/sh\n" + "# bogus shell script w/ sh-bang\n" + "exit 0\n")) + + target = self.mkdtemp() + dist = Distribution() + dist.command_obj["build"] = support.DummyCommand(build_scripts=source) + dist.command_obj["install_dist"] = support.DummyCommand( + install_scripts=target, + force=True, + skip_build=True, + ) + cmd = install_scripts(dist) + cmd.finalize_options() + cmd.run() + + installed = os.listdir(target) + for name in expected: + self.assertIn(name, installed) + + +def test_suite(): + return unittest.makeSuite(InstallScriptsTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_register.py b/Lib/packaging/tests/test_command_register.py new file mode 100644 index 0000000..7aa487a --- /dev/null +++ b/Lib/packaging/tests/test_command_register.py @@ -0,0 +1,259 @@ +"""Tests for packaging.command.register.""" +import os +import getpass +import urllib.request +import urllib.error +import urllib.parse + +try: + import docutils + DOCUTILS_SUPPORT = True +except ImportError: + DOCUTILS_SUPPORT = False + +from packaging.tests import unittest, support +from packaging.command import register as register_module +from packaging.command.register import register +from packaging.errors import PackagingSetupError + + +PYPIRC_NOPASSWORD = """\ +[distutils] + +index-servers = + server1 + +[server1] +username:me +""" + +WANTED_PYPIRC = """\ +[distutils] +index-servers = + pypi + +[pypi] +username:tarek +password:password +""" + + +class Inputs: + """Fakes user inputs.""" + def __init__(self, *answers): + self.answers = answers + self.index = 0 + + def __call__(self, prompt=''): + try: + return self.answers[self.index] + finally: + self.index += 1 + + +class FakeOpener: + """Fakes a PyPI server""" + def __init__(self): + self.reqs = [] + + def __call__(self, *args): + return self + + def open(self, req): + self.reqs.append(req) + return self + + def read(self): + return 'xxx' + + +class RegisterTestCase(support.TempdirManager, + support.EnvironRestorer, + support.LoggingCatcher, + unittest.TestCase): + + restore_environ = ['HOME'] + + def setUp(self): + super(RegisterTestCase, self).setUp() + self.tmp_dir = self.mkdtemp() + self.rc = os.path.join(self.tmp_dir, '.pypirc') + os.environ['HOME'] = self.tmp_dir + + # patching the password prompt + self._old_getpass = getpass.getpass + + def _getpass(prompt): + return 'password' + + getpass.getpass = _getpass + self.old_opener = urllib.request.build_opener + self.conn = urllib.request.build_opener = FakeOpener() + + def tearDown(self): + getpass.getpass = self._old_getpass + urllib.request.build_opener = self.old_opener + if hasattr(register_module, 'input'): + del register_module.input + super(RegisterTestCase, self).tearDown() + + def _get_cmd(self, metadata=None): + if metadata is None: + metadata = {'url': 'xxx', 'author': 'xxx', + 'author_email': 'xxx', + 'name': 'xxx', 'version': 'xxx'} + pkg_info, dist = self.create_dist(**metadata) + return register(dist) + + def test_create_pypirc(self): + # this test makes sure a .pypirc file + # is created when requested. + + # let's create a register instance + cmd = self._get_cmd() + + # we shouldn't have a .pypirc file yet + self.assertFalse(os.path.exists(self.rc)) + + # patching input and getpass.getpass + # so register gets happy + # Here's what we are faking : + # use your existing login (choice 1.) + # Username : 'tarek' + # Password : 'password' + # Save your login (y/N)? : 'y' + inputs = Inputs('1', 'tarek', 'y') + register_module.input = inputs + cmd.ensure_finalized() + cmd.run() + + # we should have a brand new .pypirc file + self.assertTrue(os.path.exists(self.rc)) + + # with the content similar to WANTED_PYPIRC + with open(self.rc) as fp: + content = fp.read() + self.assertEqual(content, WANTED_PYPIRC) + + # now let's make sure the .pypirc file generated + # really works : we shouldn't be asked anything + # if we run the command again + def _no_way(prompt=''): + raise AssertionError(prompt) + + register_module.input = _no_way + cmd.show_response = True + cmd.ensure_finalized() + cmd.run() + + # let's see what the server received : we should + # have 2 similar requests + self.assertEqual(len(self.conn.reqs), 2) + req1 = dict(self.conn.reqs[0].headers) + req2 = dict(self.conn.reqs[1].headers) + self.assertEqual(req2['Content-length'], req1['Content-length']) + self.assertIn('xxx', self.conn.reqs[1].data) + + def test_password_not_in_file(self): + + self.write_file(self.rc, PYPIRC_NOPASSWORD) + cmd = self._get_cmd() + cmd.finalize_options() + cmd._set_config() + cmd.send_metadata() + + # dist.password should be set + # therefore used afterwards by other commands + self.assertEqual(cmd.distribution.password, 'password') + + def test_registration(self): + # this test runs choice 2 + cmd = self._get_cmd() + inputs = Inputs('2', 'tarek', 'tarek@ziade.org') + register_module.input = inputs + # let's run the command + # FIXME does this send a real request? use a mock server + cmd.ensure_finalized() + cmd.run() + + # we should have send a request + self.assertEqual(len(self.conn.reqs), 1) + req = self.conn.reqs[0] + headers = dict(req.headers) + self.assertEqual(headers['Content-length'], '608') + self.assertIn('tarek', req.data) + + def test_password_reset(self): + # this test runs choice 3 + cmd = self._get_cmd() + inputs = Inputs('3', 'tarek@ziade.org') + register_module.input = inputs + cmd.ensure_finalized() + cmd.run() + + # we should have send a request + self.assertEqual(len(self.conn.reqs), 1) + req = self.conn.reqs[0] + headers = dict(req.headers) + self.assertEqual(headers['Content-length'], '290') + self.assertIn('tarek', req.data) + + @unittest.skipUnless(DOCUTILS_SUPPORT, 'needs docutils') + def test_strict(self): + # testing the script option + # when on, the register command stops if + # the metadata is incomplete or if + # long_description is not reSt compliant + + # empty metadata + cmd = self._get_cmd({'name': 'xxx', 'version': 'xxx'}) + cmd.ensure_finalized() + cmd.strict = True + inputs = Inputs('1', 'tarek', 'y') + register_module.input = inputs + self.assertRaises(PackagingSetupError, cmd.run) + + # metadata is OK but long_description is broken + metadata = {'home_page': 'xxx', 'author': 'xxx', + 'author_email': 'éxéxé', + 'name': 'xxx', 'version': 'xxx', + 'description': 'title\n==\n\ntext'} + + cmd = self._get_cmd(metadata) + cmd.ensure_finalized() + cmd.strict = True + + self.assertRaises(PackagingSetupError, cmd.run) + + # now something that works + metadata['description'] = 'title\n=====\n\ntext' + cmd = self._get_cmd(metadata) + cmd.ensure_finalized() + cmd.strict = True + inputs = Inputs('1', 'tarek', 'y') + register_module.input = inputs + cmd.ensure_finalized() + cmd.run() + + # strict is not by default + cmd = self._get_cmd() + cmd.ensure_finalized() + inputs = Inputs('1', 'tarek', 'y') + register_module.input = inputs + cmd.ensure_finalized() + cmd.run() + + def test_register_pep345(self): + cmd = self._get_cmd({}) + cmd.ensure_finalized() + cmd.distribution.metadata['Requires-Dist'] = ['lxml'] + data = cmd.build_post_data('submit') + self.assertEqual(data['metadata_version'], '1.2') + self.assertEqual(data['requires_dist'], ['lxml']) + + +def test_suite(): + return unittest.makeSuite(RegisterTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_sdist.py b/Lib/packaging/tests/test_command_sdist.py new file mode 100644 index 0000000..956e258 --- /dev/null +++ b/Lib/packaging/tests/test_command_sdist.py @@ -0,0 +1,407 @@ +"""Tests for packaging.command.sdist.""" +import os +import zipfile +import tarfile +import logging + +# zlib is not used here, but if it's not available +# the tests that use zipfile may fail +try: + import zlib +except ImportError: + zlib = None + +try: + import grp + import pwd + UID_GID_SUPPORT = True +except ImportError: + UID_GID_SUPPORT = False + +from os.path import join +from packaging.tests import captured_stdout +from packaging.command.sdist import sdist +from packaging.command.sdist import show_formats +from packaging.dist import Distribution +from packaging.tests import unittest +from packaging.errors import PackagingOptionError +from packaging.util import find_executable +from packaging.tests import support +from shutil import get_archive_formats + +SETUP_PY = """ +from packaging.core import setup +import somecode + +setup(name='fake') +""" + +MANIFEST = """\ +# file GENERATED by packaging, do NOT edit +README +inroot.txt +data%(sep)sdata.dt +scripts%(sep)sscript.py +some%(sep)sfile.txt +some%(sep)sother_file.txt +somecode%(sep)s__init__.py +somecode%(sep)sdoc.dat +somecode%(sep)sdoc.txt +""" + + +def builder(dist, filelist): + filelist.append('bah') + + +class SDistTestCase(support.TempdirManager, + support.LoggingCatcher, + support.EnvironRestorer, + unittest.TestCase): + + restore_environ = ['HOME'] + + def setUp(self): + # PyPIRCCommandTestCase creates a temp dir already + # and put it in self.tmp_dir + super(SDistTestCase, self).setUp() + self.tmp_dir = self.mkdtemp() + os.environ['HOME'] = self.tmp_dir + # setting up an environment + self.old_path = os.getcwd() + os.mkdir(join(self.tmp_dir, 'somecode')) + os.mkdir(join(self.tmp_dir, 'dist')) + # a package, and a README + self.write_file((self.tmp_dir, 'README'), 'xxx') + self.write_file((self.tmp_dir, 'somecode', '__init__.py'), '#') + self.write_file((self.tmp_dir, 'setup.py'), SETUP_PY) + os.chdir(self.tmp_dir) + + def tearDown(self): + # back to normal + os.chdir(self.old_path) + super(SDistTestCase, self).tearDown() + + def get_cmd(self, metadata=None): + """Returns a cmd""" + if metadata is None: + metadata = {'name': 'fake', 'version': '1.0', + 'url': 'xxx', 'author': 'xxx', + 'author_email': 'xxx'} + dist = Distribution(metadata) + dist.script_name = 'setup.py' + dist.packages = ['somecode'] + dist.include_package_data = True + cmd = sdist(dist) + cmd.dist_dir = 'dist' + return dist, cmd + + @unittest.skipUnless(zlib, "requires zlib") + def test_prune_file_list(self): + # this test creates a package with some vcs dirs in it + # and launch sdist to make sure they get pruned + # on all systems + + # creating VCS directories with some files in them + os.mkdir(join(self.tmp_dir, 'somecode', '.svn')) + + self.write_file((self.tmp_dir, 'somecode', '.svn', 'ok.py'), 'xxx') + + os.mkdir(join(self.tmp_dir, 'somecode', '.hg')) + self.write_file((self.tmp_dir, 'somecode', '.hg', + 'ok'), 'xxx') + + os.mkdir(join(self.tmp_dir, 'somecode', '.git')) + self.write_file((self.tmp_dir, 'somecode', '.git', + 'ok'), 'xxx') + + # now building a sdist + dist, cmd = self.get_cmd() + + # zip is available universally + # (tar might not be installed under win32) + cmd.formats = ['zip'] + + cmd.ensure_finalized() + cmd.run() + + # now let's check what we have + dist_folder = join(self.tmp_dir, 'dist') + files = os.listdir(dist_folder) + self.assertEqual(files, ['fake-1.0.zip']) + + with zipfile.ZipFile(join(dist_folder, 'fake-1.0.zip')) as zip_file: + content = zip_file.namelist() + + # making sure everything has been pruned correctly + self.assertEqual(len(content), 3) + + @unittest.skipUnless(zlib, "requires zlib") + @unittest.skipIf(find_executable('tar') is None or + find_executable('gzip') is None, + 'requires tar and gzip programs') + def test_make_distribution(self): + # building a sdist + dist, cmd = self.get_cmd() + + # creating a gztar then a tar + cmd.formats = ['gztar', 'tar'] + cmd.ensure_finalized() + cmd.run() + + # making sure we have two files + dist_folder = join(self.tmp_dir, 'dist') + result = sorted(os.listdir(dist_folder)) + self.assertEqual(result, ['fake-1.0.tar', 'fake-1.0.tar.gz']) + + os.remove(join(dist_folder, 'fake-1.0.tar')) + os.remove(join(dist_folder, 'fake-1.0.tar.gz')) + + # now trying a tar then a gztar + cmd.formats = ['tar', 'gztar'] + + cmd.ensure_finalized() + cmd.run() + + result = sorted(os.listdir(dist_folder)) + self.assertEqual(result, ['fake-1.0.tar', 'fake-1.0.tar.gz']) + + @unittest.skipUnless(zlib, "requires zlib") + def test_add_defaults(self): + + # http://bugs.python.org/issue2279 + + # add_default should also include + # data_files and package_data + dist, cmd = self.get_cmd() + + # filling data_files by pointing files + # in package_data + dist.package_data = {'': ['*.cfg', '*.dat'], + 'somecode': ['*.txt']} + self.write_file((self.tmp_dir, 'somecode', 'doc.txt'), '#') + self.write_file((self.tmp_dir, 'somecode', 'doc.dat'), '#') + + # adding some data in data_files + data_dir = join(self.tmp_dir, 'data') + os.mkdir(data_dir) + self.write_file((data_dir, 'data.dt'), '#') + some_dir = join(self.tmp_dir, 'some') + os.mkdir(some_dir) + self.write_file((self.tmp_dir, 'inroot.txt'), '#') + self.write_file((some_dir, 'file.txt'), '#') + self.write_file((some_dir, 'other_file.txt'), '#') + + dist.data_files = {'data/data.dt': '{appdata}/data.dt', + 'inroot.txt': '{appdata}/inroot.txt', + 'some/file.txt': '{appdata}/file.txt', + 'some/other_file.txt': '{appdata}/other_file.txt'} + + # adding a script + script_dir = join(self.tmp_dir, 'scripts') + os.mkdir(script_dir) + self.write_file((script_dir, 'script.py'), '#') + dist.scripts = [join('scripts', 'script.py')] + + cmd.formats = ['zip'] + cmd.use_defaults = True + + cmd.ensure_finalized() + cmd.run() + + # now let's check what we have + dist_folder = join(self.tmp_dir, 'dist') + files = os.listdir(dist_folder) + self.assertEqual(files, ['fake-1.0.zip']) + + with zipfile.ZipFile(join(dist_folder, 'fake-1.0.zip')) as zip_file: + content = zip_file.namelist() + + # Making sure everything was added. This includes 9 code and data + # files in addition to PKG-INFO. + self.assertEqual(len(content), 10) + + # Checking the MANIFEST + with open(join(self.tmp_dir, 'MANIFEST')) as fp: + manifest = fp.read() + self.assertEqual(manifest, MANIFEST % {'sep': os.sep}) + + @unittest.skipUnless(zlib, "requires zlib") + def test_metadata_check_option(self): + # testing the `check-metadata` option + dist, cmd = self.get_cmd(metadata={'name': 'xxx', 'version': 'xxx'}) + + # this should raise some warnings + # with the check subcommand + cmd.ensure_finalized() + cmd.run() + warnings = self.get_logs(logging.WARN) + self.assertEqual(len(warnings), 3) + + # trying with a complete set of metadata + self.loghandler.flush() + dist, cmd = self.get_cmd() + cmd.ensure_finalized() + cmd.metadata_check = False + cmd.run() + warnings = self.get_logs(logging.WARN) + # removing manifest generated warnings + warnings = [warn for warn in warnings if + not warn.endswith('-- skipping')] + # the remaining warning is about the use of the default file list + self.assertEqual(len(warnings), 1) + + def test_show_formats(self): + __, stdout = captured_stdout(show_formats) + + # the output should be a header line + one line per format + num_formats = len(get_archive_formats()) + output = [line for line in stdout.split('\n') + if line.strip().startswith('--formats=')] + self.assertEqual(len(output), num_formats) + + def test_finalize_options(self): + + dist, cmd = self.get_cmd() + cmd.finalize_options() + + # default options set by finalize + self.assertEqual(cmd.manifest, 'MANIFEST') + self.assertEqual(cmd.dist_dir, 'dist') + + # formats has to be a string splitable on (' ', ',') or + # a stringlist + cmd.formats = 1 + self.assertRaises(PackagingOptionError, cmd.finalize_options) + cmd.formats = ['zip'] + cmd.finalize_options() + + # formats has to be known + cmd.formats = 'supazipa' + self.assertRaises(PackagingOptionError, cmd.finalize_options) + + @unittest.skipUnless(zlib, "requires zlib") + @unittest.skipUnless(UID_GID_SUPPORT, "requires grp and pwd support") + @unittest.skipIf(find_executable('tar') is None or + find_executable('gzip') is None, + 'requires tar and gzip programs') + def test_make_distribution_owner_group(self): + # building a sdist + dist, cmd = self.get_cmd() + + # creating a gztar and specifying the owner+group + cmd.formats = ['gztar'] + cmd.owner = pwd.getpwuid(0)[0] + cmd.group = grp.getgrgid(0)[0] + cmd.ensure_finalized() + cmd.run() + + # making sure we have the good rights + archive_name = join(self.tmp_dir, 'dist', 'fake-1.0.tar.gz') + with tarfile.open(archive_name) as archive: + for member in archive.getmembers(): + self.assertEqual(member.uid, 0) + self.assertEqual(member.gid, 0) + + # building a sdist again + dist, cmd = self.get_cmd() + + # creating a gztar + cmd.formats = ['gztar'] + cmd.ensure_finalized() + cmd.run() + + # making sure we have the good rights + archive_name = join(self.tmp_dir, 'dist', 'fake-1.0.tar.gz') + with tarfile.open(archive_name) as archive: + + # note that we are not testing the group ownership here + # because, depending on the platforms and the container + # rights (see #7408) + for member in archive.getmembers(): + self.assertEqual(member.uid, os.getuid()) + + def test_get_file_list(self): + # make sure MANIFEST is recalculated + dist, cmd = self.get_cmd() + # filling data_files by pointing files in package_data + dist.package_data = {'somecode': ['*.txt']} + self.write_file((self.tmp_dir, 'somecode', 'doc.txt'), '#') + cmd.ensure_finalized() + cmd.run() + + # Should produce four lines. Those lines are one comment, one default + # (README) and two package files. + with open(cmd.manifest) as f: + manifest = [line.strip() for line in f.read().split('\n') + if line.strip() != ''] + self.assertEqual(len(manifest), 4) + + # Adding a file + self.write_file((self.tmp_dir, 'somecode', 'doc2.txt'), '#') + + # make sure build_py is reinitialized, like a fresh run + build_py = dist.get_command_obj('build_py') + build_py.finalized = False + build_py.ensure_finalized() + + cmd.run() + + with open(cmd.manifest) as f: + manifest2 = [line.strip() for line in f.read().split('\n') + if line.strip() != ''] + + # Do we have the new file in MANIFEST? + self.assertEqual(len(manifest2), 5) + self.assertIn('doc2.txt', manifest2[-1]) + + def test_manifest_marker(self): + # check that autogenerated MANIFESTs have a marker + dist, cmd = self.get_cmd() + cmd.ensure_finalized() + cmd.run() + + with open(cmd.manifest) as f: + manifest = [line.strip() for line in f.read().split('\n') + if line.strip() != ''] + + self.assertEqual(manifest[0], + '# file GENERATED by packaging, do NOT edit') + + def test_manual_manifest(self): + # check that a MANIFEST without a marker is left alone + dist, cmd = self.get_cmd() + cmd.ensure_finalized() + self.write_file((self.tmp_dir, cmd.manifest), 'README.manual') + cmd.run() + + with open(cmd.manifest) as f: + manifest = [line.strip() for line in f.read().split('\n') + if line.strip() != ''] + + self.assertEqual(manifest, ['README.manual']) + + def test_template(self): + dist, cmd = self.get_cmd() + dist.extra_files = ['include yeah'] + cmd.ensure_finalized() + self.write_file((self.tmp_dir, 'yeah'), 'xxx') + cmd.run() + with open(cmd.manifest) as f: + content = f.read() + + self.assertIn('yeah', content) + + def test_manifest_builder(self): + dist, cmd = self.get_cmd() + cmd.manifest_builders = 'packaging.tests.test_command_sdist.builder' + cmd.ensure_finalized() + cmd.run() + self.assertIn('bah', cmd.filelist.files) + + +def test_suite(): + return unittest.makeSuite(SDistTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_test.py b/Lib/packaging/tests/test_command_test.py new file mode 100644 index 0000000..4fd8452 --- /dev/null +++ b/Lib/packaging/tests/test_command_test.py @@ -0,0 +1,225 @@ +import os +import re +import sys +import shutil +import logging +import unittest as ut1 +import packaging.database + +from os.path import join +from operator import getitem, setitem, delitem +from packaging.command.build import build +from packaging.tests import unittest +from packaging.tests.support import (TempdirManager, EnvironRestorer, + LoggingCatcher) +from packaging.command.test import test +from packaging.command import set_command +from packaging.dist import Distribution + + +EXPECTED_OUTPUT_RE = r'''FAIL: test_blah \(myowntestmodule.SomeTest\) +---------------------------------------------------------------------- +Traceback \(most recent call last\): + File ".+/myowntestmodule.py", line \d+, in test_blah + self.fail\("horribly"\) +AssertionError: horribly +''' + +here = os.path.dirname(os.path.abspath(__file__)) + + +class MockBuildCmd(build): + build_lib = "mock build lib" + command_name = 'build' + plat_name = 'whatever' + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + self._record.append("build has run") + + +class TestTest(TempdirManager, + EnvironRestorer, + LoggingCatcher, + unittest.TestCase): + + restore_environ = ['PYTHONPATH'] + + def setUp(self): + super(TestTest, self).setUp() + self.addCleanup(packaging.database.clear_cache) + new_pythonpath = os.path.dirname(os.path.dirname(here)) + pythonpath = os.environ.get('PYTHONPATH') + if pythonpath is not None: + new_pythonpath = os.pathsep.join((new_pythonpath, pythonpath)) + os.environ['PYTHONPATH'] = new_pythonpath + + def assert_re_match(self, pattern, string): + def quote(s): + lines = ['## ' + line for line in s.split('\n')] + sep = ["#" * 60] + return [''] + sep + lines + sep + msg = quote(pattern) + ["didn't match"] + quote(string) + msg = "\n".join(msg) + if not re.search(pattern, string): + self.fail(msg) + + def prepare_dist(self, dist_name): + pkg_dir = join(os.path.dirname(__file__), "dists", dist_name) + temp_pkg_dir = join(self.mkdtemp(), dist_name) + shutil.copytree(pkg_dir, temp_pkg_dir) + return temp_pkg_dir + + def safely_replace(self, obj, attr, + new_val=None, delete=False, dictionary=False): + """Replace a object's attribute returning to its original state at the + end of the test run. Creates the attribute if not present before + (deleting afterwards). When delete=True, makes sure the value is del'd + for the test run. If dictionary is set to True, operates of its items + rather than attributes.""" + if dictionary: + _setattr, _getattr, _delattr = setitem, getitem, delitem + + def _hasattr(_dict, value): + return value in _dict + else: + _setattr, _getattr, _delattr, _hasattr = (setattr, getattr, + delattr, hasattr) + + orig_has_attr = _hasattr(obj, attr) + if orig_has_attr: + orig_val = _getattr(obj, attr) + + if delete is False: + _setattr(obj, attr, new_val) + elif orig_has_attr: + _delattr(obj, attr) + + def do_cleanup(): + if orig_has_attr: + _setattr(obj, attr, orig_val) + elif _hasattr(obj, attr): + _delattr(obj, attr) + + self.addCleanup(do_cleanup) + + def test_runs_unittest(self): + module_name, a_module = self.prepare_a_module() + record = [] + a_module.recorder = lambda *args: record.append("suite") + + class MockTextTestRunner: + def __init__(*_, **__): + pass + + def run(_self, suite): + record.append("run") + + self.safely_replace(ut1, "TextTestRunner", MockTextTestRunner) + + dist = Distribution() + cmd = test(dist) + cmd.suite = "%s.recorder" % module_name + cmd.run() + self.assertEqual(record, ["suite", "run"]) + + def test_builds_before_running_tests(self): + self.addCleanup(set_command, 'packaging.command.build.build') + set_command('packaging.tests.test_command_test.MockBuildCmd') + + dist = Distribution() + dist.get_command_obj('build')._record = record = [] + cmd = test(dist) + cmd.runner = self.prepare_named_function(lambda: None) + cmd.ensure_finalized() + cmd.run() + self.assertEqual(['build has run'], record) + + def _test_works_with_2to3(self): + pass + + def test_checks_requires(self): + dist = Distribution() + cmd = test(dist) + phony_project = 'ohno_ohno-impossible_1234-name_stop-that!' + cmd.tests_require = [phony_project] + cmd.ensure_finalized() + logs = self.get_logs(logging.WARNING) + self.assertEqual(1, len(logs)) + self.assertIn(phony_project, logs[0]) + + def prepare_a_module(self): + tmp_dir = self.mkdtemp() + sys.path.append(tmp_dir) + self.addCleanup(sys.path.remove, tmp_dir) + + self.write_file((tmp_dir, 'packaging_tests_a.py'), '') + import packaging_tests_a as a_module + return "packaging_tests_a", a_module + + def prepare_named_function(self, func): + module_name, a_module = self.prepare_a_module() + a_module.recorder = func + return "%s.recorder" % module_name + + def test_custom_runner(self): + dist = Distribution() + cmd = test(dist) + record = [] + cmd.runner = self.prepare_named_function( + lambda: record.append("runner called")) + cmd.ensure_finalized() + cmd.run() + self.assertEqual(["runner called"], record) + + def prepare_mock_ut2(self): + class MockUTClass: + def __init__(*_, **__): + pass + + def discover(self): + pass + + def run(self, _): + pass + + class MockUTModule: + TestLoader = MockUTClass + TextTestRunner = MockUTClass + + mock_ut2 = MockUTModule() + self.safely_replace(sys.modules, "unittest2", + mock_ut2, dictionary=True) + return mock_ut2 + + def test_gets_unittest_discovery(self): + mock_ut2 = self.prepare_mock_ut2() + dist = Distribution() + cmd = test(dist) + self.safely_replace(ut1.TestLoader, "discover", lambda: None) + self.assertEqual(cmd.get_ut_with_discovery(), ut1) + + del ut1.TestLoader.discover + self.assertEqual(cmd.get_ut_with_discovery(), mock_ut2) + + def test_calls_discover(self): + self.safely_replace(ut1.TestLoader, "discover", delete=True) + mock_ut2 = self.prepare_mock_ut2() + record = [] + mock_ut2.TestLoader.discover = lambda self, path: record.append(path) + dist = Distribution() + cmd = test(dist) + cmd.run() + self.assertEqual([os.curdir], record) + + +def test_suite(): + return unittest.makeSuite(TestTest) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_upload.py b/Lib/packaging/tests/test_command_upload.py new file mode 100644 index 0000000..f2e338b --- /dev/null +++ b/Lib/packaging/tests/test_command_upload.py @@ -0,0 +1,157 @@ +"""Tests for packaging.command.upload.""" +import os +import sys + +from packaging.command.upload import upload +from packaging.dist import Distribution +from packaging.errors import PackagingOptionError + +from packaging.tests import unittest, support +from packaging.tests.pypi_server import PyPIServer, PyPIServerTestCase + + +PYPIRC_NOPASSWORD = """\ +[distutils] + +index-servers = + server1 + +[server1] +username:me +""" + +PYPIRC = """\ +[distutils] + +index-servers = + server1 + server2 + +[server1] +username:me +password:secret + +[server2] +username:meagain +password: secret +realm:acme +repository:http://another.pypi/ +""" + + +class UploadTestCase(support.TempdirManager, support.EnvironRestorer, + support.LoggingCatcher, PyPIServerTestCase): + + restore_environ = ['HOME'] + + def setUp(self): + super(UploadTestCase, self).setUp() + self.tmp_dir = self.mkdtemp() + self.rc = os.path.join(self.tmp_dir, '.pypirc') + os.environ['HOME'] = self.tmp_dir + + def test_finalize_options(self): + # new format + self.write_file(self.rc, PYPIRC) + dist = Distribution() + cmd = upload(dist) + cmd.finalize_options() + for attr, expected in (('username', 'me'), ('password', 'secret'), + ('realm', 'pypi'), + ('repository', 'http://pypi.python.org/pypi')): + self.assertEqual(getattr(cmd, attr), expected) + + def test_finalize_options_unsigned_identity_raises_exception(self): + self.write_file(self.rc, PYPIRC) + dist = Distribution() + cmd = upload(dist) + cmd.identity = True + cmd.sign = False + self.assertRaises(PackagingOptionError, cmd.finalize_options) + + def test_saved_password(self): + # file with no password + self.write_file(self.rc, PYPIRC_NOPASSWORD) + + # make sure it passes + dist = Distribution() + cmd = upload(dist) + cmd.ensure_finalized() + self.assertEqual(cmd.password, None) + + # make sure we get it as well, if another command + # initialized it at the dist level + dist.password = 'xxx' + cmd = upload(dist) + cmd.finalize_options() + self.assertEqual(cmd.password, 'xxx') + + def test_upload_without_files_raises_exception(self): + dist = Distribution() + cmd = upload(dist) + self.assertRaises(PackagingOptionError, cmd.run) + + def test_upload(self): + path = os.path.join(self.tmp_dir, 'xxx') + self.write_file(path) + command, pyversion, filename = 'xxx', '3.3', path + dist_files = [(command, pyversion, filename)] + + # lets run it + pkg_dir, dist = self.create_dist(dist_files=dist_files, author='dédé') + cmd = upload(dist) + cmd.ensure_finalized() + cmd.repository = self.pypi.full_address + cmd.run() + + # what did we send ? + handler, request_data = self.pypi.requests[-1] + headers = handler.headers + #self.assertIn('dédé', str(request_data)) + self.assertIn(b'xxx', request_data) + + self.assertEqual(int(headers['content-length']), len(request_data)) + self.assertLess(int(headers['content-length']), 2500) + self.assertTrue(headers['content-type'].startswith('multipart/form-data')) + self.assertEqual(handler.command, 'POST') + self.assertNotIn('\n', headers['authorization']) + + def test_upload_docs(self): + path = os.path.join(self.tmp_dir, 'xxx') + self.write_file(path) + command, pyversion, filename = 'xxx', '3.3', path + dist_files = [(command, pyversion, filename)] + docs_path = os.path.join(self.tmp_dir, "build", "docs") + os.makedirs(docs_path) + self.write_file(os.path.join(docs_path, "index.html"), "yellow") + self.write_file(self.rc, PYPIRC) + + # lets run it + pkg_dir, dist = self.create_dist(dist_files=dist_files, author='dédé') + + cmd = upload(dist) + cmd.get_finalized_command("build").run() + cmd.upload_docs = True + cmd.ensure_finalized() + cmd.repository = self.pypi.full_address + try: + prev_dir = os.getcwd() + os.chdir(self.tmp_dir) + cmd.run() + finally: + os.chdir(prev_dir) + + handler, request_data = self.pypi.requests[-1] + action, name, content = request_data.split( + "----------------GHSKFJDLGDS7543FJKLFHRE75642756743254" + .encode())[1:4] + + self.assertIn(b'name=":action"', action) + self.assertIn(b'doc_upload', action) + + +def test_suite(): + return unittest.makeSuite(UploadTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_upload_docs.py b/Lib/packaging/tests/test_command_upload_docs.py new file mode 100644 index 0000000..b103894 --- /dev/null +++ b/Lib/packaging/tests/test_command_upload_docs.py @@ -0,0 +1,205 @@ +"""Tests for packaging.command.upload_docs.""" +import os +import sys +import shutil +import zipfile + +from packaging.command import upload_docs as upload_docs_mod +from packaging.command.upload_docs import (upload_docs, zip_dir, + encode_multipart) +from packaging.dist import Distribution +from packaging.errors import PackagingFileError, PackagingOptionError + +from packaging.tests import unittest, support +from packaging.tests.pypi_server import PyPIServerTestCase + + +EXPECTED_MULTIPART_OUTPUT = [ + b'---x', + b'Content-Disposition: form-data; name="username"', + b'', + b'wok', + b'---x', + b'Content-Disposition: form-data; name="password"', + b'', + b'secret', + b'---x', + b'Content-Disposition: form-data; name="picture"; filename="wok.png"', + b'', + b'PNG89', + b'---x--', + b'', +] + +PYPIRC = """\ +[distutils] +index-servers = server1 + +[server1] +repository = %s +username = real_slim_shady +password = long_island +""" + +class UploadDocsTestCase(support.TempdirManager, + support.EnvironRestorer, + support.LoggingCatcher, + PyPIServerTestCase): + + restore_environ = ['HOME'] + + def setUp(self): + super(UploadDocsTestCase, self).setUp() + self.tmp_dir = self.mkdtemp() + self.rc = os.path.join(self.tmp_dir, '.pypirc') + os.environ['HOME'] = self.tmp_dir + self.dist = Distribution() + self.dist.metadata['Name'] = "distr-name" + self.cmd = upload_docs(self.dist) + + def test_default_uploaddir(self): + sandbox = self.mkdtemp() + previous = os.getcwd() + os.chdir(sandbox) + try: + os.mkdir("build") + self.prepare_sample_dir("build") + self.cmd.ensure_finalized() + self.assertEqual(self.cmd.upload_dir, os.path.join("build", "docs")) + finally: + os.chdir(previous) + + def test_default_uploaddir_looks_for_doc_also(self): + sandbox = self.mkdtemp() + previous = os.getcwd() + os.chdir(sandbox) + try: + os.mkdir("build") + self.prepare_sample_dir("build") + os.rename(os.path.join("build", "docs"), os.path.join("build", "doc")) + self.cmd.ensure_finalized() + self.assertEqual(self.cmd.upload_dir, os.path.join("build", "doc")) + finally: + os.chdir(previous) + + def prepare_sample_dir(self, sample_dir=None): + if sample_dir is None: + sample_dir = self.mkdtemp() + os.mkdir(os.path.join(sample_dir, "docs")) + self.write_file(os.path.join(sample_dir, "docs", "index.html"), "Ce mortel ennui") + self.write_file(os.path.join(sample_dir, "index.html"), "Oh la la") + return sample_dir + + def test_zip_dir(self): + source_dir = self.prepare_sample_dir() + compressed = zip_dir(source_dir) + + zip_f = zipfile.ZipFile(compressed) + self.assertEqual(zip_f.namelist(), ['index.html', 'docs/index.html']) + + def test_encode_multipart(self): + fields = [('username', 'wok'), ('password', 'secret')] + files = [('picture', 'wok.png', b'PNG89')] + content_type, body = encode_multipart(fields, files, b'-x') + self.assertEqual(b'multipart/form-data; boundary=-x', content_type) + self.assertEqual(EXPECTED_MULTIPART_OUTPUT, body.split(b'\r\n')) + + def prepare_command(self): + self.cmd.upload_dir = self.prepare_sample_dir() + self.cmd.ensure_finalized() + self.cmd.repository = self.pypi.full_address + self.cmd.username = "username" + self.cmd.password = "password" + + def test_upload(self): + self.prepare_command() + self.cmd.run() + + self.assertEqual(len(self.pypi.requests), 1) + handler, request_data = self.pypi.requests[-1] + self.assertIn(b"content", request_data) + self.assertIn("Basic", handler.headers['authorization']) + self.assertTrue(handler.headers['content-type'] + .startswith('multipart/form-data;')) + + action, name, version, content =\ + request_data.split("----------------GHSKFJDLGDS7543FJKLFHRE75642756743254".encode())[1:5] + + + # check that we picked the right chunks + self.assertIn(b'name=":action"', action) + self.assertIn(b'name="name"', name) + self.assertIn(b'name="version"', version) + self.assertIn(b'name="content"', content) + + # check their contents + self.assertIn(b'doc_upload', action) + self.assertIn(b'distr-name', name) + self.assertIn(b'docs/index.html', content) + self.assertIn(b'Ce mortel ennui', content) + + def test_https_connection(self): + https_called = False + + orig_https = upload_docs_mod.http.client.HTTPConnection + + def https_conn_wrapper(*args): + nonlocal https_called + https_called = True + # the testing server is http + return upload_docs_mod.http.client.HTTPConnection(*args) + + upload_docs_mod.http.client.HTTPSConnection = https_conn_wrapper + try: + self.prepare_command() + self.cmd.run() + self.assertFalse(https_called) + + self.cmd.repository = self.cmd.repository.replace("http", "https") + self.cmd.run() + self.assertTrue(https_called) + finally: + upload_docs_mod.http.client.HTTPConnection = orig_https + + def test_handling_response(self): + self.pypi.default_response_status = '403 Forbidden' + self.prepare_command() + self.cmd.run() + self.assertIn('Upload failed (403): Forbidden', self.get_logs()[-1]) + + self.pypi.default_response_status = '301 Moved Permanently' + self.pypi.default_response_headers.append(("Location", "brand_new_location")) + self.cmd.run() + self.assertIn('brand_new_location', self.get_logs()[-1]) + + def test_reads_pypirc_data(self): + self.write_file(self.rc, PYPIRC % self.pypi.full_address) + self.cmd.repository = self.pypi.full_address + self.cmd.upload_dir = self.prepare_sample_dir() + self.cmd.ensure_finalized() + self.assertEqual(self.cmd.username, "real_slim_shady") + self.assertEqual(self.cmd.password, "long_island") + + def test_checks_index_html_presence(self): + self.cmd.upload_dir = self.prepare_sample_dir() + os.remove(os.path.join(self.cmd.upload_dir, "index.html")) + self.assertRaises(PackagingFileError, self.cmd.ensure_finalized) + + def test_checks_upload_dir(self): + self.cmd.upload_dir = self.prepare_sample_dir() + shutil.rmtree(os.path.join(self.cmd.upload_dir)) + self.assertRaises(PackagingOptionError, self.cmd.ensure_finalized) + + def test_show_response(self): + self.prepare_command() + self.cmd.show_response = True + self.cmd.run() + record = self.get_logs()[-1] + self.assertTrue(record, "should report the response") + self.assertIn(self.pypi.default_response_data, record) + +def test_suite(): + return unittest.makeSuite(UploadDocsTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_compiler.py b/Lib/packaging/tests/test_compiler.py new file mode 100644 index 0000000..2c620cb --- /dev/null +++ b/Lib/packaging/tests/test_compiler.py @@ -0,0 +1,66 @@ +"""Tests for distutils.compiler.""" +import os + +from packaging.compiler import (get_default_compiler, customize_compiler, + gen_lib_options) +from packaging.tests import unittest, support + + +class FakeCompiler: + + name = 'fake' + description = 'Fake' + + def library_dir_option(self, dir): + return "-L" + dir + + def runtime_library_dir_option(self, dir): + return ["-cool", "-R" + dir] + + def find_library_file(self, dirs, lib, debug=False): + return 'found' + + def library_option(self, lib): + return "-l" + lib + + +class CompilerTestCase(support.EnvironRestorer, unittest.TestCase): + + restore_environ = ['AR', 'ARFLAGS'] + + @unittest.skipUnless(get_default_compiler() == 'unix', + 'irrelevant if default compiler is not unix') + def test_customize_compiler(self): + + os.environ['AR'] = 'my_ar' + os.environ['ARFLAGS'] = '-arflags' + + # make sure AR gets caught + class compiler: + name = 'unix' + + def set_executables(self, **kw): + self.exes = kw + + comp = compiler() + customize_compiler(comp) + self.assertEqual(comp.exes['archiver'], 'my_ar -arflags') + + def test_gen_lib_options(self): + compiler = FakeCompiler() + libdirs = ['lib1', 'lib2'] + runlibdirs = ['runlib1'] + libs = [os.path.join('dir', 'name'), 'name2'] + + opts = gen_lib_options(compiler, libdirs, runlibdirs, libs) + wanted = ['-Llib1', '-Llib2', '-cool', '-Rrunlib1', 'found', + '-lname2'] + self.assertEqual(opts, wanted) + + +def test_suite(): + return unittest.makeSuite(CompilerTestCase) + + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_config.py b/Lib/packaging/tests/test_config.py new file mode 100644 index 0000000..8908c4f --- /dev/null +++ b/Lib/packaging/tests/test_config.py @@ -0,0 +1,424 @@ +"""Tests for packaging.config.""" +import os +import sys +import logging +from io import StringIO + +from packaging import command +from packaging.dist import Distribution +from packaging.errors import PackagingFileError +from packaging.compiler import new_compiler, _COMPILERS +from packaging.command.sdist import sdist + +from packaging.tests import unittest, support + + +SETUP_CFG = """ +[metadata] +name = RestingParrot +version = 0.6.4 +author = Carl Meyer +author_email = carl@oddbird.net +maintainer = Éric Araujo +maintainer_email = merwok@netwok.org +summary = A sample project demonstrating packaging +description-file = %(description-file)s +keywords = packaging, sample project + +classifier = + Development Status :: 4 - Beta + Environment :: Console (Text Based) + Environment :: X11 Applications :: GTK; python_version < '3' + License :: OSI Approved :: MIT License + Programming Language :: Python + Programming Language :: Python :: 2 + Programming Language :: Python :: 3 + +requires_python = >=2.4, <3.2 + +requires_dist = + PetShoppe + MichaelPalin (> 1.1) + pywin32; sys.platform == 'win32' + pysqlite2; python_version < '2.5' + inotify (0.0.1); sys.platform == 'linux2' + +requires_external = libxml2 + +provides_dist = packaging-sample-project (0.2) + unittest2-sample-project + +project_url = + Main repository, http://bitbucket.org/carljm/sample-distutils2-project + Fork in progress, http://bitbucket.org/Merwok/sample-distutils2-project + +[files] +packages_root = src + +packages = one + two + three + +modules = haven + +scripts = + script1.py + scripts/find-coconuts + bin/taunt + +package_data = + cheese = data/templates/* + +extra_files = %(extra-files)s + +# Replaces MANIFEST.in +sdist_extra = + include THANKS HACKING + recursive-include examples *.txt *.py + prune examples/sample?/build + +resources= + bm/ {b1,b2}.gif = {icon} + Cf*/ *.CFG = {config}/baBar/ + init_script = {script}/JunGle/ + +[global] +commands = + packaging.tests.test_config.FooBarBazTest + +compilers = + packaging.tests.test_config.DCompiler + +setup_hook = %(setup-hook)s + + + +[install_dist] +sub_commands = foo +""" + +# Can not be merged with SETUP_CFG else install_dist +# command will fail when trying to compile C sources +EXT_SETUP_CFG = """ +[files] +packages = one + two + +[extension=speed_coconuts] +name = one.speed_coconuts +sources = c_src/speed_coconuts.c +extra_link_args = "`gcc -print-file-name=libgcc.a`" -shared +define_macros = HAVE_CAIRO HAVE_GTK2 +libraries = gecodeint gecodekernel -- sys.platform != 'win32' + GecodeInt GecodeKernel -- sys.platform == 'win32' + +[extension=fast_taunt] +name = three.fast_taunt +sources = cxx_src/utils_taunt.cxx + cxx_src/python_module.cxx +include_dirs = /usr/include/gecode + /usr/include/blitz +extra_compile_args = -fPIC -O2 + -DGECODE_VERSION=$(./gecode_version) -- sys.platform != 'win32' + /DGECODE_VERSION='win32' -- sys.platform == 'win32' +language = cxx + +""" + + +class DCompiler: + name = 'd' + description = 'D Compiler' + + def __init__(self, *args): + pass + + +def hook(content): + content['metadata']['version'] += '.dev1' + + +class FooBarBazTest: + + def __init__(self, dist): + self.distribution = dist + + @classmethod + def get_command_name(cls): + return 'foo' + + def run(self): + self.distribution.foo_was_here = True + + def nothing(self): + pass + + def get_source_files(self): + return [] + + ensure_finalized = finalize_options = initialize_options = nothing + + +class ConfigTestCase(support.TempdirManager, + support.EnvironRestorer, + support.LoggingCatcher, + unittest.TestCase): + + restore_environ = ['PLAT'] + + def setUp(self): + super(ConfigTestCase, self).setUp() + self.addCleanup(setattr, sys, 'stdout', sys.stdout) + self.addCleanup(setattr, sys, 'stderr', sys.stderr) + sys.stdout = StringIO() + sys.stderr = StringIO() + + self.addCleanup(os.chdir, os.getcwd()) + tempdir = self.mkdtemp() + os.chdir(tempdir) + self.tempdir = tempdir + + def write_setup(self, kwargs=None): + opts = {'description-file': 'README', 'extra-files': '', + 'setup-hook': 'packaging.tests.test_config.hook'} + if kwargs: + opts.update(kwargs) + self.write_file('setup.cfg', SETUP_CFG % opts) + + def get_dist(self): + dist = Distribution() + dist.parse_config_files() + return dist + + def test_config(self): + self.write_setup() + self.write_file('README', 'yeah') + os.mkdir('bm') + self.write_file(('bm', 'b1.gif'), '') + self.write_file(('bm', 'b2.gif'), '') + os.mkdir('Cfg') + self.write_file(('Cfg', 'data.CFG'), '') + self.write_file('init_script', '') + + # try to load the metadata now + dist = self.get_dist() + + # check what was done + self.assertEqual(dist.metadata['Author'], 'Carl Meyer') + self.assertEqual(dist.metadata['Author-Email'], 'carl@oddbird.net') + + # the hook adds .dev1 + self.assertEqual(dist.metadata['Version'], '0.6.4.dev1') + + wanted = [ + 'Development Status :: 4 - Beta', + 'Environment :: Console (Text Based)', + "Environment :: X11 Applications :: GTK; python_version < '3'", + 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 3'] + self.assertEqual(dist.metadata['Classifier'], wanted) + + wanted = ['packaging', 'sample project'] + self.assertEqual(dist.metadata['Keywords'], wanted) + + self.assertEqual(dist.metadata['Requires-Python'], '>=2.4, <3.2') + + wanted = ['PetShoppe', + 'MichaelPalin (> 1.1)', + "pywin32; sys.platform == 'win32'", + "pysqlite2; python_version < '2.5'", + "inotify (0.0.1); sys.platform == 'linux2'"] + + self.assertEqual(dist.metadata['Requires-Dist'], wanted) + urls = [('Main repository', + 'http://bitbucket.org/carljm/sample-distutils2-project'), + ('Fork in progress', + 'http://bitbucket.org/Merwok/sample-distutils2-project')] + self.assertEqual(dist.metadata['Project-Url'], urls) + + self.assertEqual(dist.packages, ['one', 'two', 'three']) + self.assertEqual(dist.py_modules, ['haven']) + self.assertEqual(dist.package_data, {'cheese': 'data/templates/*'}) + self.assertEqual( + {'bm/b1.gif': '{icon}/b1.gif', + 'bm/b2.gif': '{icon}/b2.gif', + 'Cfg/data.CFG': '{config}/baBar/data.CFG', + 'init_script': '{script}/JunGle/init_script'}, + dist.data_files) + + self.assertEqual(dist.package_dir, 'src') + + # Make sure we get the foo command loaded. We use a string comparison + # instead of assertIsInstance because the class is not the same when + # this test is run directly: foo is packaging.tests.test_config.Foo + # because get_command_class uses the full name, but a bare "Foo" in + # this file would be __main__.Foo when run as "python test_config.py". + # The name FooBarBazTest should be unique enough to prevent + # collisions. + self.assertEqual('FooBarBazTest', + dist.get_command_obj('foo').__class__.__name__) + + # did the README got loaded ? + self.assertEqual(dist.metadata['description'], 'yeah') + + # do we have the D Compiler enabled ? + self.assertIn('d', _COMPILERS) + d = new_compiler(compiler='d') + self.assertEqual(d.description, 'D Compiler') + + def test_multiple_description_file(self): + self.write_setup({'description-file': 'README CHANGES'}) + self.write_file('README', 'yeah') + self.write_file('CHANGES', 'changelog2') + dist = self.get_dist() + self.assertEqual(dist.metadata.requires_files, ['README', 'CHANGES']) + + def test_multiline_description_file(self): + self.write_setup({'description-file': 'README\n CHANGES'}) + self.write_file('README', 'yeah') + self.write_file('CHANGES', 'changelog') + dist = self.get_dist() + self.assertEqual(dist.metadata['description'], 'yeah\nchangelog') + self.assertEqual(dist.metadata.requires_files, ['README', 'CHANGES']) + + def test_parse_extensions_in_config(self): + self.write_file('setup.cfg', EXT_SETUP_CFG) + dist = self.get_dist() + + ext_modules = dict((mod.name, mod) for mod in dist.ext_modules) + self.assertEqual(len(ext_modules), 2) + ext = ext_modules.get('one.speed_coconuts') + self.assertEqual(ext.sources, ['c_src/speed_coconuts.c']) + self.assertEqual(ext.define_macros, ['HAVE_CAIRO', 'HAVE_GTK2']) + libs = ['gecodeint', 'gecodekernel'] + if sys.platform == 'win32': + libs = ['GecodeInt', 'GecodeKernel'] + self.assertEqual(ext.libraries, libs) + self.assertEqual(ext.extra_link_args, + ['`gcc -print-file-name=libgcc.a`', '-shared']) + + ext = ext_modules.get('three.fast_taunt') + self.assertEqual(ext.sources, + ['cxx_src/utils_taunt.cxx', 'cxx_src/python_module.cxx']) + self.assertEqual(ext.include_dirs, + ['/usr/include/gecode', '/usr/include/blitz']) + cargs = ['-fPIC', '-O2'] + if sys.platform == 'win32': + cargs.append("/DGECODE_VERSION='win32'") + else: + cargs.append('-DGECODE_VERSION=$(./gecode_version)') + self.assertEqual(ext.extra_compile_args, cargs) + self.assertEqual(ext.language, 'cxx') + + def test_missing_setuphook_warns(self): + self.write_setup({'setup-hook': 'this.does._not.exist'}) + self.write_file('README', 'yeah') + dist = self.get_dist() + logs = self.get_logs(logging.WARNING) + self.assertEqual(1, len(logs)) + self.assertIn('could not import setup_hook', logs[0]) + + def test_metadata_requires_description_files_missing(self): + self.write_setup({'description-file': 'README\n README2'}) + self.write_file('README', 'yeah') + self.write_file('README2', 'yeah') + os.mkdir('src') + self.write_file(('src', 'haven.py'), '#') + self.write_file('script1.py', '#') + os.mkdir('scripts') + self.write_file(('scripts', 'find-coconuts'), '#') + os.mkdir('bin') + self.write_file(('bin', 'taunt'), '#') + + for pkg in ('one', 'two', 'three'): + pkg = os.path.join('src', pkg) + os.mkdir(pkg) + self.write_file((pkg, '__init__.py'), '#') + + dist = self.get_dist() + cmd = sdist(dist) + cmd.finalize_options() + cmd.get_file_list() + self.assertRaises(PackagingFileError, cmd.make_distribution) + + def test_metadata_requires_description_files(self): + # Create the following file structure: + # README + # README2 + # script1.py + # scripts/ + # find-coconuts + # bin/ + # taunt + # src/ + # haven.py + # one/__init__.py + # two/__init__.py + # three/__init__.py + + self.write_setup({'description-file': 'README\n README2', + 'extra-files': '\n README3'}) + self.write_file('README', 'yeah 1') + self.write_file('README2', 'yeah 2') + self.write_file('README3', 'yeah 3') + os.mkdir('src') + self.write_file(('src', 'haven.py'), '#') + self.write_file('script1.py', '#') + os.mkdir('scripts') + self.write_file(('scripts', 'find-coconuts'), '#') + os.mkdir('bin') + self.write_file(('bin', 'taunt'), '#') + + for pkg in ('one', 'two', 'three'): + pkg = os.path.join('src', pkg) + os.mkdir(pkg) + self.write_file((pkg, '__init__.py'), '#') + + dist = self.get_dist() + self.assertIn('yeah 1\nyeah 2', dist.metadata['description']) + + cmd = sdist(dist) + cmd.finalize_options() + cmd.get_file_list() + self.assertRaises(PackagingFileError, cmd.make_distribution) + + self.write_setup({'description-file': 'README\n README2', + 'extra-files': '\n README2\n README'}) + dist = self.get_dist() + cmd = sdist(dist) + cmd.finalize_options() + cmd.get_file_list() + cmd.make_distribution() + with open('MANIFEST') as fp: + self.assertIn('README\nREADME2\n', fp.read()) + + def test_sub_commands(self): + self.write_setup() + self.write_file('README', 'yeah') + os.mkdir('src') + self.write_file(('src', 'haven.py'), '#') + self.write_file('script1.py', '#') + os.mkdir('scripts') + self.write_file(('scripts', 'find-coconuts'), '#') + os.mkdir('bin') + self.write_file(('bin', 'taunt'), '#') + + for pkg in ('one', 'two', 'three'): + pkg = os.path.join('src', pkg) + os.mkdir(pkg) + self.write_file((pkg, '__init__.py'), '#') + + # try to run the install command to see if foo is called + dist = self.get_dist() + self.assertIn('foo', command.get_command_names()) + self.assertEqual('FooBarBazTest', + dist.get_command_obj('foo').__class__.__name__) + + +def test_suite(): + return unittest.makeSuite(ConfigTestCase) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/Lib/packaging/tests/test_create.py b/Lib/packaging/tests/test_create.py new file mode 100644 index 0000000..99ab063 --- /dev/null +++ b/Lib/packaging/tests/test_create.py @@ -0,0 +1,235 @@ +"""Tests for packaging.create.""" +import io +import os +import sys +import sysconfig +from textwrap import dedent +from packaging.create import MainProgram, ask_yn, ask, main + +from packaging.tests import support, unittest + + +class CreateTestCase(support.TempdirManager, + support.EnvironRestorer, + unittest.TestCase): + + restore_environ = ['PLAT'] + + def setUp(self): + super(CreateTestCase, self).setUp() + self._stdin = sys.stdin # TODO use Inputs + self._stdout = sys.stdout + sys.stdin = io.StringIO() + sys.stdout = io.StringIO() + self._cwd = os.getcwd() + self.wdir = self.mkdtemp() + os.chdir(self.wdir) + # patch sysconfig + self._old_get_paths = sysconfig.get_paths + sysconfig.get_paths = lambda *args, **kwargs: { + 'man': sys.prefix + '/share/man', + 'doc': sys.prefix + '/share/doc/pyxfoil', } + + def tearDown(self): + super(CreateTestCase, self).tearDown() + sys.stdin = self._stdin + sys.stdout = self._stdout + os.chdir(self._cwd) + sysconfig.get_paths = self._old_get_paths + + def test_ask_yn(self): + sys.stdin.write('y\n') + sys.stdin.seek(0) + self.assertEqual('y', ask_yn('is this a test')) + + def test_ask(self): + sys.stdin.write('a\n') + sys.stdin.write('b\n') + sys.stdin.seek(0) + self.assertEqual('a', ask('is this a test')) + self.assertEqual('b', ask(str(list(range(0, 70))), default='c', + lengthy=True)) + + def test_set_multi(self): + mainprogram = MainProgram() + sys.stdin.write('aaaaa\n') + sys.stdin.seek(0) + mainprogram.data['author'] = [] + mainprogram._set_multi('_set_multi test', 'author') + self.assertEqual(['aaaaa'], mainprogram.data['author']) + + def test_find_files(self): + # making sure we scan a project dir correctly + mainprogram = MainProgram() + + # building the structure + tempdir = self.wdir + dirs = ['pkg1', 'data', 'pkg2', 'pkg2/sub'] + files = ['README', 'setup.cfg', 'foo.py', + 'pkg1/__init__.py', 'pkg1/bar.py', + 'data/data1', 'pkg2/__init__.py', + 'pkg2/sub/__init__.py'] + + for dir_ in dirs: + os.mkdir(os.path.join(tempdir, dir_)) + + for file_ in files: + path = os.path.join(tempdir, file_) + self.write_file(path, 'xxx') + + mainprogram._find_files() + mainprogram.data['packages'].sort() + + # do we have what we want? + self.assertEqual(mainprogram.data['packages'], + ['pkg1', 'pkg2', 'pkg2.sub']) + self.assertEqual(mainprogram.data['modules'], ['foo']) + data_fn = os.path.join('data', 'data1') + self.assertEqual(set(mainprogram.data['extra_files']), + set(['setup.cfg', 'README', data_fn])) + + def test_convert_setup_py_to_cfg(self): + self.write_file((self.wdir, 'setup.py'), + dedent(""" + # -*- coding: utf-8 -*- + from distutils.core import setup + + long_description = '''My super Death-scription + barbar is now on the public domain, + ho, baby !''' + + setup(name='pyxfoil', + version='0.2', + description='Python bindings for the Xfoil engine', + long_description=long_description, + maintainer='André Espaze', + maintainer_email='andre.espaze@logilab.fr', + url='http://www.python-science.org/project/pyxfoil', + license='GPLv2', + packages=['pyxfoil', 'babar', 'me'], + data_files=[ + ('share/doc/pyxfoil', ['README.rst']), + ('share/man', ['pyxfoil.1']), + ], + py_modules=['my_lib', 'mymodule'], + package_dir={ + 'babar': '', + 'me': 'Martinique/Lamentin', + }, + package_data={ + 'babar': ['Pom', 'Flora', 'Alexander'], + 'me': ['dady', 'mumy', 'sys', 'bro'], + '': ['setup.py', 'README'], + 'pyxfoil': ['fengine.so'], + }, + scripts=['my_script', 'bin/run'], + ) + """)) + sys.stdin.write('y\n') + sys.stdin.seek(0) + main() + + with open(os.path.join(self.wdir, 'setup.cfg')) as fp: + lines = set(line.rstrip() for line in fp) + + # FIXME don't use sets + self.assertEqual(lines, set(['', + '[metadata]', + 'version = 0.2', + 'name = pyxfoil', + 'maintainer = André Espaze', + 'description = My super Death-scription', + ' |barbar is now on the public domain,', + ' |ho, baby !', + 'maintainer_email = andre.espaze@logilab.fr', + 'home_page = http://www.python-science.org/project/pyxfoil', + 'download_url = UNKNOWN', + 'summary = Python bindings for the Xfoil engine', + '[files]', + 'modules = my_lib', + ' mymodule', + 'packages = pyxfoil', + ' babar', + ' me', + 'extra_files = Martinique/Lamentin/dady', + ' Martinique/Lamentin/mumy', + ' Martinique/Lamentin/sys', + ' Martinique/Lamentin/bro', + ' Pom', + ' Flora', + ' Alexander', + ' setup.py', + ' README', + ' pyxfoil/fengine.so', + 'scripts = my_script', + ' bin/run', + 'resources =', + ' README.rst = {doc}', + ' pyxfoil.1 = {man}', + ])) + + def test_convert_setup_py_to_cfg_with_description_in_readme(self): + self.write_file((self.wdir, 'setup.py'), + dedent(""" + # -*- coding: utf-8 -*- + from distutils.core import setup + fp = open('README.txt') + try: + long_description = fp.read() + finally: + fp.close() + + setup(name='pyxfoil', + version='0.2', + description='Python bindings for the Xfoil engine', + long_description=long_description, + maintainer='André Espaze', + maintainer_email='andre.espaze@logilab.fr', + url='http://www.python-science.org/project/pyxfoil', + license='GPLv2', + packages=['pyxfoil'], + package_data={'pyxfoil': ['fengine.so', 'babar.so']}, + data_files=[ + ('share/doc/pyxfoil', ['README.rst']), + ('share/man', ['pyxfoil.1']), + ], + ) + """)) + self.write_file((self.wdir, 'README.txt'), + dedent(''' +My super Death-scription +barbar is now in the public domain, +ho, baby! + ''')) + sys.stdin.write('y\n') + sys.stdin.seek(0) + # FIXME Out of memory error. + main() + with open(os.path.join(self.wdir, 'setup.cfg')) as fp: + lines = set(line.rstrip() for line in fp) + + self.assertEqual(lines, set(['', + '[metadata]', + 'version = 0.2', + 'name = pyxfoil', + 'maintainer = André Espaze', + 'maintainer_email = andre.espaze@logilab.fr', + 'home_page = http://www.python-science.org/project/pyxfoil', + 'download_url = UNKNOWN', + 'summary = Python bindings for the Xfoil engine', + 'description-file = README.txt', + '[files]', + 'packages = pyxfoil', + 'extra_files = pyxfoil/fengine.so', + ' pyxfoil/babar.so', + 'resources =', + ' README.rst = {doc}', + ' pyxfoil.1 = {man}', + ])) + + +def test_suite(): + return unittest.makeSuite(CreateTestCase) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/Lib/packaging/tests/test_cygwinccompiler.py b/Lib/packaging/tests/test_cygwinccompiler.py new file mode 100644 index 0000000..17c43cd --- /dev/null +++ b/Lib/packaging/tests/test_cygwinccompiler.py @@ -0,0 +1,88 @@ +"""Tests for packaging.cygwinccompiler.""" +import os +import sys +import sysconfig +from packaging.compiler.cygwinccompiler import ( + check_config_h, get_msvcr, + CONFIG_H_OK, CONFIG_H_NOTOK, CONFIG_H_UNCERTAIN) + +from packaging.tests import unittest, support + + +class CygwinCCompilerTestCase(support.TempdirManager, + unittest.TestCase): + + def setUp(self): + super(CygwinCCompilerTestCase, self).setUp() + self.version = sys.version + self.python_h = os.path.join(self.mkdtemp(), 'python.h') + self.old_get_config_h_filename = sysconfig.get_config_h_filename + sysconfig.get_config_h_filename = self._get_config_h_filename + + def tearDown(self): + sys.version = self.version + sysconfig.get_config_h_filename = self.old_get_config_h_filename + super(CygwinCCompilerTestCase, self).tearDown() + + def _get_config_h_filename(self): + return self.python_h + + def test_check_config_h(self): + # check_config_h looks for "GCC" in sys.version first + # returns CONFIG_H_OK if found + sys.version = ('2.6.1 (r261:67515, Dec 6 2008, 16:42:21) \n[GCC ' + '4.0.1 (Apple Computer, Inc. build 5370)]') + + self.assertEqual(check_config_h()[0], CONFIG_H_OK) + + # then it tries to see if it can find "__GNUC__" in pyconfig.h + sys.version = 'something without the *CC word' + + # if the file doesn't exist it returns CONFIG_H_UNCERTAIN + self.assertEqual(check_config_h()[0], CONFIG_H_UNCERTAIN) + + # if it exists but does not contain __GNUC__, it returns CONFIG_H_NOTOK + self.write_file(self.python_h, 'xxx') + self.assertEqual(check_config_h()[0], CONFIG_H_NOTOK) + + # and CONFIG_H_OK if __GNUC__ is found + self.write_file(self.python_h, 'xxx __GNUC__ xxx') + self.assertEqual(check_config_h()[0], CONFIG_H_OK) + + def test_get_msvcr(self): + # none + sys.version = ('2.6.1 (r261:67515, Dec 6 2008, 16:42:21) ' + '\n[GCC 4.0.1 (Apple Computer, Inc. build 5370)]') + self.assertEqual(get_msvcr(), None) + + # MSVC 7.0 + sys.version = ('2.5.1 (r251:54863, Apr 18 2007, 08:51:08) ' + '[MSC v.1300 32 bits (Intel)]') + self.assertEqual(get_msvcr(), ['msvcr70']) + + # MSVC 7.1 + sys.version = ('2.5.1 (r251:54863, Apr 18 2007, 08:51:08) ' + '[MSC v.1310 32 bits (Intel)]') + self.assertEqual(get_msvcr(), ['msvcr71']) + + # VS2005 / MSVC 8.0 + sys.version = ('2.5.1 (r251:54863, Apr 18 2007, 08:51:08) ' + '[MSC v.1400 32 bits (Intel)]') + self.assertEqual(get_msvcr(), ['msvcr80']) + + # VS2008 / MSVC 9.0 + sys.version = ('2.5.1 (r251:54863, Apr 18 2007, 08:51:08) ' + '[MSC v.1500 32 bits (Intel)]') + self.assertEqual(get_msvcr(), ['msvcr90']) + + # unknown + sys.version = ('2.5.1 (r251:54863, Apr 18 2007, 08:51:08) ' + '[MSC v.1999 32 bits (Intel)]') + self.assertRaises(ValueError, get_msvcr) + + +def test_suite(): + return unittest.makeSuite(CygwinCCompilerTestCase) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/Lib/packaging/tests/test_database.py b/Lib/packaging/tests/test_database.py new file mode 100644 index 0000000..c8d9415 --- /dev/null +++ b/Lib/packaging/tests/test_database.py @@ -0,0 +1,506 @@ +import os +import io +import csv +import imp +import sys +import shutil +import zipfile +import tempfile +from os.path import relpath # separate import for backport concerns +from hashlib import md5 + +from packaging.errors import PackagingError +from packaging.metadata import Metadata +from packaging.tests import unittest, run_unittest, support, TESTFN + +from packaging.database import ( + Distribution, EggInfoDistribution, get_distribution, get_distributions, + provides_distribution, obsoletes_distribution, get_file_users, + enable_cache, disable_cache, distinfo_dirname, _yield_distributions) + +# TODO Add a test for getting a distribution provided by another distribution +# TODO Add a test for absolute pathed RECORD items (e.g. /etc/myapp/config.ini) +# TODO Add tests from the former pep376 project (zipped site-packages, etc.) + + +def get_hexdigest(filename): + with open(filename, 'rb') as file: + checksum = md5(file.read()) + return checksum.hexdigest() + + +def record_pieces(file): + path = relpath(file, sys.prefix) + digest = get_hexdigest(file) + size = os.path.getsize(file) + return [path, digest, size] + + +class CommonDistributionTests: + """Mixin used to test the interface common to both Distribution classes. + + Derived classes define cls, sample_dist, dirs and records. These + attributes are used in test methods. See source code for details. + """ + + def setUp(self): + super(CommonDistributionTests, self).setUp() + self.addCleanup(enable_cache) + disable_cache() + self.fake_dists_path = os.path.abspath( + os.path.join(os.path.dirname(__file__), 'fake_dists')) + + def test_instantiation(self): + # check that useful attributes are here + name, version, distdir = self.sample_dist + here = os.path.abspath(os.path.dirname(__file__)) + dist_path = os.path.join(here, 'fake_dists', distdir) + + dist = self.dist = self.cls(dist_path) + self.assertEqual(dist.path, dist_path) + self.assertEqual(dist.name, name) + self.assertEqual(dist.metadata['Name'], name) + self.assertIsInstance(dist.metadata, Metadata) + self.assertEqual(dist.version, version) + self.assertEqual(dist.metadata['Version'], version) + + def test_repr(self): + dist = self.cls(self.dirs[0]) + # just check that the class name is in the repr + self.assertIn(self.cls.__name__, repr(dist)) + + def test_comparison(self): + # tests for __eq__ and __hash__ + dist = self.cls(self.dirs[0]) + dist2 = self.cls(self.dirs[0]) + dist3 = self.cls(self.dirs[1]) + self.assertIn(dist, {dist: True}) + self.assertEqual(dist, dist) + + self.assertIsNot(dist, dist2) + self.assertEqual(dist, dist2) + self.assertNotEqual(dist, dist3) + self.assertNotEqual(dist, ()) + + def test_list_installed_files(self): + for dir_ in self.dirs: + dist = self.cls(dir_) + for path, md5_, size in dist.list_installed_files(): + record_data = self.records[dist.path] + self.assertIn(path, record_data) + self.assertEqual(md5_, record_data[path][0]) + self.assertEqual(size, record_data[path][1]) + + +class TestDistribution(CommonDistributionTests, unittest.TestCase): + + cls = Distribution + sample_dist = 'choxie', '2.0.0.9', 'choxie-2.0.0.9.dist-info' + + def setUp(self): + super(TestDistribution, self).setUp() + self.dirs = [os.path.join(self.fake_dists_path, f) + for f in os.listdir(self.fake_dists_path) + if f.endswith('.dist-info')] + + self.records = {} + for distinfo_dir in self.dirs: + record_file = os.path.join(distinfo_dir, 'RECORD') + with open(record_file, 'w') as file: + record_writer = csv.writer( + file, delimiter=',', quoting=csv.QUOTE_NONE) + + dist_location = distinfo_dir.replace('.dist-info', '') + + for path, dirs, files in os.walk(dist_location): + for f in files: + record_writer.writerow(record_pieces( + os.path.join(path, f))) + for file in ('INSTALLER', 'METADATA', 'REQUESTED'): + record_writer.writerow(record_pieces( + os.path.join(distinfo_dir, file))) + record_writer.writerow([relpath(record_file, sys.prefix)]) + + with open(record_file) as file: + record_reader = csv.reader(file) + record_data = {} + for row in record_reader: + path, md5_, size = (row[:] + + [None for i in range(len(row), 3)]) + record_data[path] = md5_, size + self.records[distinfo_dir] = record_data + + def tearDown(self): + for distinfo_dir in self.dirs: + record_file = os.path.join(distinfo_dir, 'RECORD') + open(record_file, 'w').close() + super(TestDistribution, self).tearDown() + + def test_instantiation(self): + super(TestDistribution, self).test_instantiation() + self.assertIsInstance(self.dist.requested, bool) + + def test_uses(self): + # Test to determine if a distribution uses a specified file. + # Criteria to test against + distinfo_name = 'grammar-1.0a4' + distinfo_dir = os.path.join(self.fake_dists_path, + distinfo_name + '.dist-info') + true_path = [self.fake_dists_path, distinfo_name, + 'grammar', 'utils.py'] + true_path = relpath(os.path.join(*true_path), sys.prefix) + false_path = [self.fake_dists_path, 'towel_stuff-0.1', 'towel_stuff', + '__init__.py'] + false_path = relpath(os.path.join(*false_path), sys.prefix) + + # Test if the distribution uses the file in question + dist = Distribution(distinfo_dir) + self.assertTrue(dist.uses(true_path)) + self.assertFalse(dist.uses(false_path)) + + def test_get_distinfo_file(self): + # Test the retrieval of dist-info file objects. + distinfo_name = 'choxie-2.0.0.9' + other_distinfo_name = 'grammar-1.0a4' + distinfo_dir = os.path.join(self.fake_dists_path, + distinfo_name + '.dist-info') + dist = Distribution(distinfo_dir) + # Test for known good file matches + distinfo_files = [ + # Relative paths + 'INSTALLER', 'METADATA', + # Absolute paths + os.path.join(distinfo_dir, 'RECORD'), + os.path.join(distinfo_dir, 'REQUESTED'), + ] + + for distfile in distinfo_files: + with dist.get_distinfo_file(distfile) as value: + self.assertIsInstance(value, io.TextIOWrapper) + # Is it the correct file? + self.assertEqual(value.name, + os.path.join(distinfo_dir, distfile)) + + # Test an absolute path that is part of another distributions dist-info + other_distinfo_file = os.path.join( + self.fake_dists_path, other_distinfo_name + '.dist-info', + 'REQUESTED') + self.assertRaises(PackagingError, dist.get_distinfo_file, + other_distinfo_file) + # Test for a file that should not exist + self.assertRaises(PackagingError, dist.get_distinfo_file, + 'MAGICFILE') + + def test_list_distinfo_files(self): + # Test for the iteration of RECORD path entries. + distinfo_name = 'towel_stuff-0.1' + distinfo_dir = os.path.join(self.fake_dists_path, + distinfo_name + '.dist-info') + dist = Distribution(distinfo_dir) + # Test for the iteration of the raw path + distinfo_record_paths = self.records[distinfo_dir].keys() + found = dist.list_distinfo_files() + self.assertEqual(sorted(found), sorted(distinfo_record_paths)) + # Test for the iteration of local absolute paths + distinfo_record_paths = [os.path.join(sys.prefix, path) + for path in self.records[distinfo_dir]] + found = dist.list_distinfo_files(local=True) + self.assertEqual(sorted(found), sorted(distinfo_record_paths)) + + def test_get_resources_path(self): + distinfo_name = 'babar-0.1' + distinfo_dir = os.path.join(self.fake_dists_path, + distinfo_name + '.dist-info') + dist = Distribution(distinfo_dir) + resource_path = dist.get_resource_path('babar.png') + self.assertEqual(resource_path, 'babar.png') + self.assertRaises(KeyError, dist.get_resource_path, 'notexist') + + +class TestEggInfoDistribution(CommonDistributionTests, + support.LoggingCatcher, + unittest.TestCase): + + cls = EggInfoDistribution + sample_dist = 'bacon', '0.1', 'bacon-0.1.egg-info' + + def setUp(self): + super(TestEggInfoDistribution, self).setUp() + + self.dirs = [os.path.join(self.fake_dists_path, f) + for f in os.listdir(self.fake_dists_path) + if f.endswith('.egg') or f.endswith('.egg-info')] + + self.records = {} + + @unittest.skip('not implemented yet') + def test_list_installed_files(self): + # EggInfoDistribution defines list_installed_files but there is no + # test for it yet; someone with setuptools expertise needs to add a + # file with the list of installed files for one of the egg fake dists + # and write the support code to populate self.records (and then delete + # this method) + pass + + +class TestDatabase(support.LoggingCatcher, + unittest.TestCase): + + def setUp(self): + super(TestDatabase, self).setUp() + disable_cache() + # Setup the path environment with our fake distributions + current_path = os.path.abspath(os.path.dirname(__file__)) + self.sys_path = sys.path[:] + self.fake_dists_path = os.path.join(current_path, 'fake_dists') + sys.path.insert(0, self.fake_dists_path) + + def tearDown(self): + sys.path[:] = self.sys_path + enable_cache() + super(TestDatabase, self).tearDown() + + def test_distinfo_dirname(self): + # Given a name and a version, we expect the distinfo_dirname function + # to return a standard distribution information directory name. + + items = [ + # (name, version, standard_dirname) + # Test for a very simple single word name and decimal version + # number + ('docutils', '0.5', 'docutils-0.5.dist-info'), + # Test for another except this time with a '-' in the name, which + # needs to be transformed during the name lookup + ('python-ldap', '2.5', 'python_ldap-2.5.dist-info'), + # Test for both '-' in the name and a funky version number + ('python-ldap', '2.5 a---5', 'python_ldap-2.5 a---5.dist-info'), + ] + + # Loop through the items to validate the results + for name, version, standard_dirname in items: + dirname = distinfo_dirname(name, version) + self.assertEqual(dirname, standard_dirname) + + def test_get_distributions(self): + # Lookup all distributions found in the ``sys.path``. + # This test could potentially pick up other installed distributions + fake_dists = [('grammar', '1.0a4'), ('choxie', '2.0.0.9'), + ('towel-stuff', '0.1'), ('babar', '0.1')] + found_dists = [] + + # Verify the fake dists have been found. + dists = [dist for dist in get_distributions()] + for dist in dists: + self.assertIsInstance(dist, Distribution) + if (dist.name in dict(fake_dists) and + dist.path.startswith(self.fake_dists_path)): + found_dists.append((dist.name, dist.metadata['version'], )) + else: + # check that it doesn't find anything more than this + self.assertFalse(dist.path.startswith(self.fake_dists_path)) + # otherwise we don't care what other distributions are found + + # Finally, test that we found all that we were looking for + self.assertEqual(sorted(found_dists), sorted(fake_dists)) + + # Now, test if the egg-info distributions are found correctly as well + fake_dists += [('bacon', '0.1'), ('cheese', '2.0.2'), + ('coconuts-aster', '10.3'), + ('banana', '0.4'), ('strawberry', '0.6'), + ('truffles', '5.0'), ('nut', 'funkyversion')] + found_dists = [] + + dists = [dist for dist in get_distributions(use_egg_info=True)] + for dist in dists: + self.assertIsInstance(dist, (Distribution, EggInfoDistribution)) + if (dist.name in dict(fake_dists) and + dist.path.startswith(self.fake_dists_path)): + found_dists.append((dist.name, dist.metadata['version'])) + else: + self.assertFalse(dist.path.startswith(self.fake_dists_path)) + + self.assertEqual(sorted(fake_dists), sorted(found_dists)) + + def test_get_distribution(self): + # Test for looking up a distribution by name. + # Test the lookup of the towel-stuff distribution + name = 'towel-stuff' # Note: This is different from the directory name + + # Lookup the distribution + dist = get_distribution(name) + self.assertIsInstance(dist, Distribution) + self.assertEqual(dist.name, name) + + # Verify that an unknown distribution returns None + self.assertIsNone(get_distribution('bogus')) + + # Verify partial name matching doesn't work + self.assertIsNone(get_distribution('towel')) + + # Verify that it does not find egg-info distributions, when not + # instructed to + self.assertIsNone(get_distribution('bacon')) + self.assertIsNone(get_distribution('cheese')) + self.assertIsNone(get_distribution('strawberry')) + self.assertIsNone(get_distribution('banana')) + + # Now check that it works well in both situations, when egg-info + # is a file and directory respectively. + dist = get_distribution('cheese', use_egg_info=True) + self.assertIsInstance(dist, EggInfoDistribution) + self.assertEqual(dist.name, 'cheese') + + dist = get_distribution('bacon', use_egg_info=True) + self.assertIsInstance(dist, EggInfoDistribution) + self.assertEqual(dist.name, 'bacon') + + dist = get_distribution('banana', use_egg_info=True) + self.assertIsInstance(dist, EggInfoDistribution) + self.assertEqual(dist.name, 'banana') + + dist = get_distribution('strawberry', use_egg_info=True) + self.assertIsInstance(dist, EggInfoDistribution) + self.assertEqual(dist.name, 'strawberry') + + def test_get_file_users(self): + # Test the iteration of distributions that use a file. + name = 'towel_stuff-0.1' + path = os.path.join(self.fake_dists_path, name, + 'towel_stuff', '__init__.py') + for dist in get_file_users(path): + self.assertIsInstance(dist, Distribution) + self.assertEqual(dist.name, name) + + def test_provides(self): + # Test for looking up distributions by what they provide + checkLists = lambda x, y: self.assertEqual(sorted(x), sorted(y)) + + l = [dist.name for dist in provides_distribution('truffles')] + checkLists(l, ['choxie', 'towel-stuff']) + + l = [dist.name for dist in provides_distribution('truffles', '1.0')] + checkLists(l, ['choxie']) + + l = [dist.name for dist in provides_distribution('truffles', '1.0', + use_egg_info=True)] + checkLists(l, ['choxie', 'cheese']) + + l = [dist.name for dist in provides_distribution('truffles', '1.1.2')] + checkLists(l, ['towel-stuff']) + + l = [dist.name for dist in provides_distribution('truffles', '1.1')] + checkLists(l, ['towel-stuff']) + + l = [dist.name for dist in provides_distribution('truffles', + '!=1.1,<=2.0')] + checkLists(l, ['choxie']) + + l = [dist.name for dist in provides_distribution('truffles', + '!=1.1,<=2.0', + use_egg_info=True)] + checkLists(l, ['choxie', 'bacon', 'cheese']) + + l = [dist.name for dist in provides_distribution('truffles', '>1.0')] + checkLists(l, ['towel-stuff']) + + l = [dist.name for dist in provides_distribution('truffles', '>1.5')] + checkLists(l, []) + + l = [dist.name for dist in provides_distribution('truffles', '>1.5', + use_egg_info=True)] + checkLists(l, ['bacon']) + + l = [dist.name for dist in provides_distribution('truffles', '>=1.0')] + checkLists(l, ['choxie', 'towel-stuff']) + + l = [dist.name for dist in provides_distribution('strawberry', '0.6', + use_egg_info=True)] + checkLists(l, ['coconuts-aster']) + + l = [dist.name for dist in provides_distribution('strawberry', '>=0.5', + use_egg_info=True)] + checkLists(l, ['coconuts-aster']) + + l = [dist.name for dist in provides_distribution('strawberry', '>0.6', + use_egg_info=True)] + checkLists(l, []) + + l = [dist.name for dist in provides_distribution('banana', '0.4', + use_egg_info=True)] + checkLists(l, ['coconuts-aster']) + + l = [dist.name for dist in provides_distribution('banana', '>=0.3', + use_egg_info=True)] + checkLists(l, ['coconuts-aster']) + + l = [dist.name for dist in provides_distribution('banana', '!=0.4', + use_egg_info=True)] + checkLists(l, []) + + def test_obsoletes(self): + # Test looking for distributions based on what they obsolete + checkLists = lambda x, y: self.assertEqual(sorted(x), sorted(y)) + + l = [dist.name for dist in obsoletes_distribution('truffles', '1.0')] + checkLists(l, []) + + l = [dist.name for dist in obsoletes_distribution('truffles', '1.0', + use_egg_info=True)] + checkLists(l, ['cheese', 'bacon']) + + l = [dist.name for dist in obsoletes_distribution('truffles', '0.8')] + checkLists(l, ['choxie']) + + l = [dist.name for dist in obsoletes_distribution('truffles', '0.8', + use_egg_info=True)] + checkLists(l, ['choxie', 'cheese']) + + l = [dist.name for dist in obsoletes_distribution('truffles', '0.9.6')] + checkLists(l, ['choxie', 'towel-stuff']) + + l = [dist.name for dist in obsoletes_distribution('truffles', + '0.5.2.3')] + checkLists(l, ['choxie', 'towel-stuff']) + + l = [dist.name for dist in obsoletes_distribution('truffles', '0.2')] + checkLists(l, ['towel-stuff']) + + def test_yield_distribution(self): + # tests the internal function _yield_distributions + checkLists = lambda x, y: self.assertEqual(sorted(x), sorted(y)) + + eggs = [('bacon', '0.1'), ('banana', '0.4'), ('strawberry', '0.6'), + ('truffles', '5.0'), ('cheese', '2.0.2'), + ('coconuts-aster', '10.3'), ('nut', 'funkyversion')] + dists = [('choxie', '2.0.0.9'), ('grammar', '1.0a4'), + ('towel-stuff', '0.1'), ('babar', '0.1')] + + checkLists([], _yield_distributions(False, False)) + + found = [(dist.name, dist.metadata['Version']) + for dist in _yield_distributions(False, True) + if dist.path.startswith(self.fake_dists_path)] + checkLists(eggs, found) + + found = [(dist.name, dist.metadata['Version']) + for dist in _yield_distributions(True, False) + if dist.path.startswith(self.fake_dists_path)] + checkLists(dists, found) + + found = [(dist.name, dist.metadata['Version']) + for dist in _yield_distributions(True, True) + if dist.path.startswith(self.fake_dists_path)] + checkLists(dists + eggs, found) + + +def test_suite(): + suite = unittest.TestSuite() + load = unittest.defaultTestLoader.loadTestsFromTestCase + suite.addTest(load(TestDistribution)) + suite.addTest(load(TestEggInfoDistribution)) + suite.addTest(load(TestDatabase)) + return suite + + +if __name__ == "__main__": + unittest.main(defaultTest='test_suite') diff --git a/Lib/packaging/tests/test_depgraph.py b/Lib/packaging/tests/test_depgraph.py new file mode 100644 index 0000000..9271a7b --- /dev/null +++ b/Lib/packaging/tests/test_depgraph.py @@ -0,0 +1,301 @@ +"""Tests for packaging.depgraph """ +import io +import os +import re +import sys +import packaging.database +from packaging import depgraph + +from packaging.tests import unittest, support + + +class DepGraphTestCase(support.LoggingCatcher, + unittest.TestCase): + + DISTROS_DIST = ('choxie', 'grammar', 'towel-stuff') + DISTROS_EGG = ('bacon', 'banana', 'strawberry', 'cheese') + BAD_EGGS = ('nut',) + + EDGE = re.compile( + r'"(?P<from>.*)" -> "(?P<to>.*)" \[label="(?P<label>.*)"\]') + + def checkLists(self, l1, l2): + """ Compare two lists without taking the order into consideration """ + self.assertListEqual(sorted(l1), sorted(l2)) + + def setUp(self): + super(DepGraphTestCase, self).setUp() + path = os.path.join(os.path.dirname(__file__), 'fake_dists') + path = os.path.abspath(path) + sys.path.insert(0, path) + self.addCleanup(sys.path.remove, path) + self.addCleanup(packaging.database.enable_cache) + packaging.database.disable_cache() + + def test_generate_graph(self): + dists = [] + for name in self.DISTROS_DIST: + dist = packaging.database.get_distribution(name) + self.assertNotEqual(dist, None) + dists.append(dist) + + choxie, grammar, towel = dists + + graph = depgraph.generate_graph(dists) + + deps = [(x.name, y) for x, y in graph.adjacency_list[choxie]] + self.checkLists([('towel-stuff', 'towel-stuff (0.1)')], deps) + self.assertIn(choxie, graph.reverse_list[towel]) + self.checkLists(graph.missing[choxie], ['nut']) + + deps = [(x.name, y) for x, y in graph.adjacency_list[grammar]] + self.checkLists([], deps) + self.checkLists(graph.missing[grammar], ['truffles (>=1.2)']) + + deps = [(x.name, y) for x, y in graph.adjacency_list[towel]] + self.checkLists([], deps) + self.checkLists(graph.missing[towel], ['bacon (<=0.2)']) + + def test_generate_graph_egg(self): + dists = [] + for name in self.DISTROS_DIST + self.DISTROS_EGG: + dist = packaging.database.get_distribution(name, use_egg_info=True) + self.assertNotEqual(dist, None) + dists.append(dist) + + choxie, grammar, towel, bacon, banana, strawberry, cheese = dists + + graph = depgraph.generate_graph(dists) + + deps = [(x.name, y) for x, y in graph.adjacency_list[choxie]] + self.checkLists([('towel-stuff', 'towel-stuff (0.1)')], deps) + self.assertIn(choxie, graph.reverse_list[towel]) + self.checkLists(graph.missing[choxie], ['nut']) + + deps = [(x.name, y) for x, y in graph.adjacency_list[grammar]] + self.checkLists([('bacon', 'truffles (>=1.2)')], deps) + self.checkLists(graph.missing[grammar], []) + self.assertIn(grammar, graph.reverse_list[bacon]) + + deps = [(x.name, y) for x, y in graph.adjacency_list[towel]] + self.checkLists([('bacon', 'bacon (<=0.2)')], deps) + self.checkLists(graph.missing[towel], []) + self.assertIn(towel, graph.reverse_list[bacon]) + + deps = [(x.name, y) for x, y in graph.adjacency_list[bacon]] + self.checkLists([], deps) + self.checkLists(graph.missing[bacon], []) + + deps = [(x.name, y) for x, y in graph.adjacency_list[banana]] + self.checkLists([('strawberry', 'strawberry (>=0.5)')], deps) + self.checkLists(graph.missing[banana], []) + self.assertIn(banana, graph.reverse_list[strawberry]) + + deps = [(x.name, y) for x, y in graph.adjacency_list[strawberry]] + self.checkLists([], deps) + self.checkLists(graph.missing[strawberry], []) + + deps = [(x.name, y) for x, y in graph.adjacency_list[cheese]] + self.checkLists([], deps) + self.checkLists(graph.missing[cheese], []) + + def test_dependent_dists(self): + dists = [] + for name in self.DISTROS_DIST: + dist = packaging.database.get_distribution(name) + self.assertNotEqual(dist, None) + dists.append(dist) + + choxie, grammar, towel = dists + + deps = [d.name for d in depgraph.dependent_dists(dists, choxie)] + self.checkLists([], deps) + + deps = [d.name for d in depgraph.dependent_dists(dists, grammar)] + self.checkLists([], deps) + + deps = [d.name for d in depgraph.dependent_dists(dists, towel)] + self.checkLists(['choxie'], deps) + + def test_dependent_dists_egg(self): + dists = [] + for name in self.DISTROS_DIST + self.DISTROS_EGG: + dist = packaging.database.get_distribution(name, use_egg_info=True) + self.assertNotEqual(dist, None) + dists.append(dist) + + choxie, grammar, towel, bacon, banana, strawberry, cheese = dists + + deps = [d.name for d in depgraph.dependent_dists(dists, choxie)] + self.checkLists([], deps) + + deps = [d.name for d in depgraph.dependent_dists(dists, grammar)] + self.checkLists([], deps) + + deps = [d.name for d in depgraph.dependent_dists(dists, towel)] + self.checkLists(['choxie'], deps) + + deps = [d.name for d in depgraph.dependent_dists(dists, bacon)] + self.checkLists(['choxie', 'towel-stuff', 'grammar'], deps) + + deps = [d.name for d in depgraph.dependent_dists(dists, strawberry)] + self.checkLists(['banana'], deps) + + deps = [d.name for d in depgraph.dependent_dists(dists, cheese)] + self.checkLists([], deps) + + def test_graph_to_dot(self): + expected = ( + ('towel-stuff', 'bacon', 'bacon (<=0.2)'), + ('grammar', 'bacon', 'truffles (>=1.2)'), + ('choxie', 'towel-stuff', 'towel-stuff (0.1)'), + ('banana', 'strawberry', 'strawberry (>=0.5)'), + ) + + dists = [] + for name in self.DISTROS_DIST + self.DISTROS_EGG: + dist = packaging.database.get_distribution(name, use_egg_info=True) + self.assertNotEqual(dist, None) + dists.append(dist) + + graph = depgraph.generate_graph(dists) + buf = io.StringIO() + depgraph.graph_to_dot(graph, buf) + buf.seek(0) + matches = [] + lines = buf.readlines() + for line in lines[1:-1]: # skip the first and the last lines + if line[-1] == '\n': + line = line[:-1] + match = self.EDGE.match(line.strip()) + self.assertIsNot(match, None) + matches.append(match.groups()) + + self.checkLists(matches, expected) + + def test_graph_disconnected_to_dot(self): + dependencies_expected = ( + ('towel-stuff', 'bacon', 'bacon (<=0.2)'), + ('grammar', 'bacon', 'truffles (>=1.2)'), + ('choxie', 'towel-stuff', 'towel-stuff (0.1)'), + ('banana', 'strawberry', 'strawberry (>=0.5)'), + ) + disconnected_expected = ('cheese', 'bacon', 'strawberry') + + dists = [] + for name in self.DISTROS_DIST + self.DISTROS_EGG: + dist = packaging.database.get_distribution(name, use_egg_info=True) + self.assertNotEqual(dist, None) + dists.append(dist) + + graph = depgraph.generate_graph(dists) + buf = io.StringIO() + depgraph.graph_to_dot(graph, buf, skip_disconnected=False) + buf.seek(0) + lines = buf.readlines() + + dependencies_lines = [] + disconnected_lines = [] + + # First sort output lines into dependencies and disconnected lines. + # We also skip the attribute lines, and don't include the "{" and "}" + # lines. + disconnected_active = False + for line in lines[1:-1]: # Skip first and last line + if line.startswith('subgraph disconnected'): + disconnected_active = True + continue + if line.startswith('}') and disconnected_active: + disconnected_active = False + continue + + if disconnected_active: + # Skip the 'label = "Disconnected"', etc. attribute lines. + if ' = ' not in line: + disconnected_lines.append(line) + else: + dependencies_lines.append(line) + + dependencies_matches = [] + for line in dependencies_lines: + if line[-1] == '\n': + line = line[:-1] + match = self.EDGE.match(line.strip()) + self.assertIsNot(match, None) + dependencies_matches.append(match.groups()) + + disconnected_matches = [] + for line in disconnected_lines: + if line[-1] == '\n': + line = line[:-1] + line = line.strip('"') + disconnected_matches.append(line) + + self.checkLists(dependencies_matches, dependencies_expected) + self.checkLists(disconnected_matches, disconnected_expected) + + def test_graph_bad_version_to_dot(self): + expected = ( + ('towel-stuff', 'bacon', 'bacon (<=0.2)'), + ('grammar', 'bacon', 'truffles (>=1.2)'), + ('choxie', 'towel-stuff', 'towel-stuff (0.1)'), + ('banana', 'strawberry', 'strawberry (>=0.5)'), + ) + + dists = [] + for name in self.DISTROS_DIST + self.DISTROS_EGG + self.BAD_EGGS: + dist = packaging.database.get_distribution(name, use_egg_info=True) + self.assertNotEqual(dist, None) + dists.append(dist) + + graph = depgraph.generate_graph(dists) + buf = io.StringIO() + depgraph.graph_to_dot(graph, buf) + buf.seek(0) + matches = [] + lines = buf.readlines() + for line in lines[1:-1]: # skip the first and the last lines + if line[-1] == '\n': + line = line[:-1] + match = self.EDGE.match(line.strip()) + self.assertIsNot(match, None) + matches.append(match.groups()) + + self.checkLists(matches, expected) + + def test_repr(self): + dists = [] + for name in self.DISTROS_DIST + self.DISTROS_EGG + self.BAD_EGGS: + dist = packaging.database.get_distribution(name, use_egg_info=True) + self.assertNotEqual(dist, None) + dists.append(dist) + + graph = depgraph.generate_graph(dists) + self.assertTrue(repr(graph)) + + def test_main(self): + tempout = io.StringIO() + old = sys.stdout + sys.stdout = tempout + oldargv = sys.argv[:] + sys.argv[:] = ['script.py'] + try: + try: + depgraph.main() + except SystemExit: + pass + finally: + sys.stdout = old + sys.argv[:] = oldargv + + # checks what main did XXX could do more here + tempout.seek(0) + res = tempout.read() + self.assertIn('towel', res) + + +def test_suite(): + return unittest.makeSuite(DepGraphTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_dist.py b/Lib/packaging/tests/test_dist.py new file mode 100644 index 0000000..77dab71 --- /dev/null +++ b/Lib/packaging/tests/test_dist.py @@ -0,0 +1,445 @@ +"""Tests for packaging.dist.""" +import os +import io +import sys +import logging +import textwrap +import packaging.dist + +from packaging.dist import Distribution +from packaging.command import set_command +from packaging.command.cmd import Command +from packaging.errors import PackagingModuleError, PackagingOptionError +from packaging.tests import TESTFN, captured_stdout +from packaging.tests import support, unittest +from packaging.tests.support import create_distribution + + +class test_dist(Command): + """Sample packaging extension command.""" + + user_options = [ + ("sample-option=", "S", "help text"), + ] + + def initialize_options(self): + self.sample_option = None + + def finalize_options(self): + pass + + +class DistributionTestCase(support.TempdirManager, + support.LoggingCatcher, + support.EnvironRestorer, + unittest.TestCase): + + restore_environ = ['HOME'] + + def setUp(self): + super(DistributionTestCase, self).setUp() + self.argv = sys.argv, sys.argv[:] + del sys.argv[1:] + + def tearDown(self): + sys.argv = self.argv[0] + sys.argv[:] = self.argv[1] + super(DistributionTestCase, self).tearDown() + + def test_debug_mode(self): + self.addCleanup(os.unlink, TESTFN) + with open(TESTFN, "w") as f: + f.write("[global]\n") + f.write("command_packages = foo.bar, splat") + + files = [TESTFN] + sys.argv.append("build") + __, stdout = captured_stdout(create_distribution, files) + self.assertEqual(stdout, '') + packaging.dist.DEBUG = True + try: + __, stdout = captured_stdout(create_distribution, files) + self.assertEqual(stdout, '') + finally: + packaging.dist.DEBUG = False + + def test_write_pkg_file(self): + # Check Metadata handling of Unicode fields + tmp_dir = self.mkdtemp() + my_file = os.path.join(tmp_dir, 'f') + cls = Distribution + + dist = cls(attrs={'author': 'Mister Café', + 'name': 'my.package', + 'maintainer': 'Café Junior', + 'summary': 'Café torréfié', + 'description': 'Héhéhé'}) + + # let's make sure the file can be written + # with Unicode fields. they are encoded with + # PKG_INFO_ENCODING + with open(my_file, 'w') as fp: + dist.metadata.write_file(fp) + + # regular ascii is of course always usable + dist = cls(attrs={'author': 'Mister Cafe', + 'name': 'my.package', + 'maintainer': 'Cafe Junior', + 'summary': 'Cafe torrefie', + 'description': 'Hehehe'}) + + with open(my_file, 'w') as fp: + dist.metadata.write_file(fp) + + def test_bad_attr(self): + Distribution(attrs={'author': 'xxx', + 'name': 'xxx', + 'version': '1.2', + 'url': 'xxxx', + 'badoptname': 'xxx'}) + logs = self.get_logs(logging.WARNING) + self.assertEqual(1, len(logs)) + self.assertIn('unknown argument', logs[0]) + + def test_bad_version(self): + Distribution(attrs={'author': 'xxx', + 'name': 'xxx', + 'version': 'xxx', + 'url': 'xxxx'}) + logs = self.get_logs(logging.WARNING) + self.assertEqual(1, len(logs)) + self.assertIn('not a valid version', logs[0]) + + def test_empty_options(self): + # an empty options dictionary should not stay in the + # list of attributes + Distribution(attrs={'author': 'xxx', + 'name': 'xxx', + 'version': '1.2', + 'url': 'xxxx', + 'options': {}}) + + self.assertEqual([], self.get_logs(logging.WARNING)) + + def test_non_empty_options(self): + # TODO: how to actually use options is not documented except + # for a few cryptic comments in dist.py. If this is to stay + # in the public API, it deserves some better documentation. + + # Here is an example of how it's used out there: + # http://svn.pythonmac.org/py2app/py2app/trunk/doc/ + # index.html#specifying-customizations + dist = Distribution(attrs={'author': 'xxx', + 'name': 'xxx', + 'version': 'xxx', + 'url': 'xxxx', + 'options': {'sdist': {'owner': 'root'}}}) + + self.assertIn('owner', dist.get_option_dict('sdist')) + + def test_finalize_options(self): + + attrs = {'keywords': 'one,two', + 'platform': 'one,two'} + + dist = Distribution(attrs=attrs) + dist.finalize_options() + + # finalize_option splits platforms and keywords + self.assertEqual(dist.metadata['platform'], ['one', 'two']) + self.assertEqual(dist.metadata['keywords'], ['one', 'two']) + + def test_find_config_files_disable(self): + # Bug #1180: Allow users to disable their own config file. + temp_home = self.mkdtemp() + if os.name == 'posix': + user_filename = os.path.join(temp_home, ".pydistutils.cfg") + else: + user_filename = os.path.join(temp_home, "pydistutils.cfg") + + with open(user_filename, 'w') as f: + f.write('[distutils2]\n') + + def _expander(path): + return temp_home + + old_expander = os.path.expanduser + os.path.expanduser = _expander + try: + d = packaging.dist.Distribution() + all_files = d.find_config_files() + + d = packaging.dist.Distribution(attrs={'script_args': + ['--no-user-cfg']}) + files = d.find_config_files() + finally: + os.path.expanduser = old_expander + + # make sure --no-user-cfg disables the user cfg file + self.assertEqual((len(all_files) - 1), len(files)) + + def test_special_hooks_parsing(self): + temp_home = self.mkdtemp() + config_files = [os.path.join(temp_home, "config1.cfg"), + os.path.join(temp_home, "config2.cfg")] + + # Store two aliased hooks in config files + self.write_file((temp_home, "config1.cfg"), + '[test_dist]\npre-hook.a = type') + self.write_file((temp_home, "config2.cfg"), + '[test_dist]\npre-hook.b = type') + + set_command('packaging.tests.test_dist.test_dist') + dist = create_distribution(config_files) + cmd = dist.get_command_obj("test_dist") + self.assertEqual(cmd.pre_hook, {"a": 'type', "b": 'type'}) + + def test_hooks_get_run(self): + temp_home = self.mkdtemp() + config_file = os.path.join(temp_home, "config1.cfg") + hooks_module = os.path.join(temp_home, "testhooks.py") + + self.write_file(config_file, textwrap.dedent(''' + [test_dist] + pre-hook.test = testhooks.log_pre_call + post-hook.test = testhooks.log_post_call''')) + + self.write_file(hooks_module, textwrap.dedent(''' + record = [] + + def log_pre_call(cmd): + record.append('pre-%s' % cmd.get_command_name()) + + def log_post_call(cmd): + record.append('post-%s' % cmd.get_command_name()) + ''')) + + set_command('packaging.tests.test_dist.test_dist') + d = create_distribution([config_file]) + cmd = d.get_command_obj("test_dist") + + # prepare the call recorders + sys.path.append(temp_home) + self.addCleanup(sys.path.remove, temp_home) + from testhooks import record + + cmd.run = lambda: record.append('run') + cmd.finalize_options = lambda: record.append('finalize') + + d.run_command('test_dist') + + self.assertEqual(record, ['finalize', + 'pre-test_dist', + 'run', + 'post-test_dist']) + + def test_hooks_importable(self): + temp_home = self.mkdtemp() + config_file = os.path.join(temp_home, "config1.cfg") + + self.write_file(config_file, textwrap.dedent(''' + [test_dist] + pre-hook.test = nonexistent.dotted.name''')) + + set_command('packaging.tests.test_dist.test_dist') + d = create_distribution([config_file]) + cmd = d.get_command_obj("test_dist") + cmd.ensure_finalized() + + self.assertRaises(PackagingModuleError, d.run_command, 'test_dist') + + def test_hooks_callable(self): + temp_home = self.mkdtemp() + config_file = os.path.join(temp_home, "config1.cfg") + + self.write_file(config_file, textwrap.dedent(''' + [test_dist] + pre-hook.test = packaging.tests.test_dist.__doc__''')) + + set_command('packaging.tests.test_dist.test_dist') + d = create_distribution([config_file]) + cmd = d.get_command_obj("test_dist") + cmd.ensure_finalized() + + self.assertRaises(PackagingOptionError, d.run_command, 'test_dist') + + +class MetadataTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + def setUp(self): + super(MetadataTestCase, self).setUp() + self.argv = sys.argv, sys.argv[:] + + def tearDown(self): + sys.argv = self.argv[0] + sys.argv[:] = self.argv[1] + super(MetadataTestCase, self).tearDown() + + def test_simple_metadata(self): + attrs = {"name": "package", + "version": "1.0"} + dist = Distribution(attrs) + meta = self.format_metadata(dist) + self.assertIn("Metadata-Version: 1.0", meta) + self.assertNotIn("provides:", meta.lower()) + self.assertNotIn("requires:", meta.lower()) + self.assertNotIn("obsoletes:", meta.lower()) + + def test_provides_dist(self): + attrs = {"name": "package", + "version": "1.0", + "provides_dist": ["package", "package.sub"]} + dist = Distribution(attrs) + self.assertEqual(dist.metadata['Provides-Dist'], + ["package", "package.sub"]) + meta = self.format_metadata(dist) + self.assertIn("Metadata-Version: 1.2", meta) + self.assertNotIn("requires:", meta.lower()) + self.assertNotIn("obsoletes:", meta.lower()) + + def _test_provides_illegal(self): + # XXX to do: check the versions + self.assertRaises(ValueError, Distribution, + {"name": "package", + "version": "1.0", + "provides_dist": ["my.pkg (splat)"]}) + + def test_requires_dist(self): + attrs = {"name": "package", + "version": "1.0", + "requires_dist": ["other", "another (==1.0)"]} + dist = Distribution(attrs) + self.assertEqual(dist.metadata['Requires-Dist'], + ["other", "another (==1.0)"]) + meta = self.format_metadata(dist) + self.assertIn("Metadata-Version: 1.2", meta) + self.assertNotIn("provides:", meta.lower()) + self.assertIn("Requires-Dist: other", meta) + self.assertIn("Requires-Dist: another (==1.0)", meta) + self.assertNotIn("obsoletes:", meta.lower()) + + def _test_requires_illegal(self): + # XXX + self.assertRaises(ValueError, Distribution, + {"name": "package", + "version": "1.0", + "requires": ["my.pkg (splat)"]}) + + def test_obsoletes_dist(self): + attrs = {"name": "package", + "version": "1.0", + "obsoletes_dist": ["other", "another (<1.0)"]} + dist = Distribution(attrs) + self.assertEqual(dist.metadata['Obsoletes-Dist'], + ["other", "another (<1.0)"]) + meta = self.format_metadata(dist) + self.assertIn("Metadata-Version: 1.2", meta) + self.assertNotIn("provides:", meta.lower()) + self.assertNotIn("requires:", meta.lower()) + self.assertIn("Obsoletes-Dist: other", meta) + self.assertIn("Obsoletes-Dist: another (<1.0)", meta) + + def _test_obsoletes_illegal(self): + # XXX + self.assertRaises(ValueError, Distribution, + {"name": "package", + "version": "1.0", + "obsoletes": ["my.pkg (splat)"]}) + + def format_metadata(self, dist): + sio = io.StringIO() + dist.metadata.write_file(sio) + return sio.getvalue() + + def test_custom_pydistutils(self): + # fixes #2166 + # make sure pydistutils.cfg is found + if os.name == 'posix': + user_filename = ".pydistutils.cfg" + else: + user_filename = "pydistutils.cfg" + + temp_dir = self.mkdtemp() + user_filename = os.path.join(temp_dir, user_filename) + with open(user_filename, 'w') as f: + f.write('.') + + dist = Distribution() + + # linux-style + if sys.platform in ('linux', 'darwin'): + os.environ['HOME'] = temp_dir + files = dist.find_config_files() + self.assertIn(user_filename, files) + + # win32-style + if sys.platform == 'win32': + # home drive should be found + os.environ['HOME'] = temp_dir + files = dist.find_config_files() + self.assertIn(user_filename, files) + + def test_show_help(self): + # smoke test, just makes sure some help is displayed + dist = Distribution() + sys.argv = [] + dist.help = True + dist.script_name = 'setup.py' + __, stdout = captured_stdout(dist.parse_command_line) + output = [line for line in stdout.split('\n') + if line.strip() != ''] + self.assertGreater(len(output), 0) + + def test_description(self): + desc = textwrap.dedent("""\ + example:: + We start here + and continue here + and end here.""") + attrs = {"name": "package", + "version": "1.0", + "description": desc} + + dist = packaging.dist.Distribution(attrs) + meta = self.format_metadata(dist) + meta = meta.replace('\n' + 7 * ' ' + '|', '\n') + self.assertIn(desc, meta) + + def test_read_metadata(self): + attrs = {"name": "package", + "version": "1.0", + "description": "desc", + "summary": "xxx", + "download_url": "http://example.com", + "keywords": ['one', 'two'], + "requires_dist": ['foo']} + + dist = Distribution(attrs) + metadata = dist.metadata + + # write it then reloads it + PKG_INFO = io.StringIO() + metadata.write_file(PKG_INFO) + PKG_INFO.seek(0) + + metadata.read_file(PKG_INFO) + self.assertEqual(metadata['name'], "package") + self.assertEqual(metadata['version'], "1.0") + self.assertEqual(metadata['summary'], "xxx") + self.assertEqual(metadata['download_url'], 'http://example.com') + self.assertEqual(metadata['keywords'], ['one', 'two']) + self.assertEqual(metadata['platform'], []) + self.assertEqual(metadata['obsoletes'], []) + self.assertEqual(metadata['requires-dist'], ['foo']) + + +def test_suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(DistributionTestCase)) + suite.addTest(unittest.makeSuite(MetadataTestCase)) + return suite + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_extension.py b/Lib/packaging/tests/test_extension.py new file mode 100644 index 0000000..41182e5 --- /dev/null +++ b/Lib/packaging/tests/test_extension.py @@ -0,0 +1,15 @@ +"""Tests for packaging.extension.""" +import os + +from packaging.compiler.extension import Extension +from packaging.tests import unittest + +class ExtensionTestCase(unittest.TestCase): + + pass + +def test_suite(): + return unittest.makeSuite(ExtensionTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_install.py b/Lib/packaging/tests/test_install.py new file mode 100644 index 0000000..2c51d19 --- /dev/null +++ b/Lib/packaging/tests/test_install.py @@ -0,0 +1,353 @@ +"""Tests for the packaging.install module.""" + +import os +from tempfile import mkstemp +from packaging import install +from packaging.pypi.xmlrpc import Client +from packaging.metadata import Metadata + +from packaging.tests.support import LoggingCatcher, TempdirManager, unittest +from packaging.tests.pypi_server import use_xmlrpc_server + + +class InstalledDist: + """Distribution object, represent distributions currently installed on the + system""" + def __init__(self, name, version, deps): + self.metadata = Metadata() + self.name = name + self.version = version + self.metadata['Name'] = name + self.metadata['Version'] = version + self.metadata['Requires-Dist'] = deps + + def __repr__(self): + return '<InstalledDist %s>' % self.metadata['Name'] + + +class ToInstallDist: + """Distribution that will be installed""" + + def __init__(self, files=False): + self._files = files + self.install_called = False + self.install_called_with = {} + self.uninstall_called = False + self._real_files = [] + self.name = "fake" + self.version = "fake" + if files: + for f in range(0, 3): + self._real_files.append(mkstemp()) + + def _unlink_installed_files(self): + if self._files: + for f in self._real_files: + os.unlink(f[1]) + + def list_installed_files(self, **args): + if self._files: + return [f[1] for f in self._real_files] + + def get_install(self, **args): + return self.list_installed_files() + + +class MagicMock: + def __init__(self, return_value=None, raise_exception=False): + self.called = False + self._times_called = 0 + self._called_with = [] + self._return_value = return_value + self._raise = raise_exception + + def __call__(self, *args, **kwargs): + self.called = True + self._times_called = self._times_called + 1 + self._called_with.append((args, kwargs)) + iterable = hasattr(self._raise, '__iter__') + if self._raise: + if ((not iterable and self._raise) + or self._raise[self._times_called - 1]): + raise Exception + return self._return_value + + def called_with(self, *args, **kwargs): + return (args, kwargs) in self._called_with + + +def get_installed_dists(dists): + """Return a list of fake installed dists. + The list is name, version, deps""" + objects = [] + for name, version, deps in dists: + objects.append(InstalledDist(name, version, deps)) + return objects + + +class TestInstall(LoggingCatcher, TempdirManager, unittest.TestCase): + def _get_client(self, server, *args, **kwargs): + return Client(server.full_address, *args, **kwargs) + + def _get_results(self, output): + """return a list of results""" + installed = [(o.name, str(o.version)) for o in output['install']] + remove = [(o.name, str(o.version)) for o in output['remove']] + conflict = [(o.name, str(o.version)) for o in output['conflict']] + return installed, remove, conflict + + @use_xmlrpc_server() + def test_existing_deps(self, server): + # Test that the installer get the dependencies from the metadatas + # and ask the index for this dependencies. + # In this test case, we have choxie that is dependent from towel-stuff + # 0.1, which is in-turn dependent on bacon <= 0.2: + # choxie -> towel-stuff -> bacon. + # Each release metadata is not provided in metadata 1.2. + client = self._get_client(server) + archive_path = '%s/distribution.tar.gz' % server.full_address + server.xmlrpc.set_distributions([ + {'name': 'choxie', + 'version': '2.0.0.9', + 'requires_dist': ['towel-stuff (0.1)'], + 'url': archive_path}, + {'name': 'towel-stuff', + 'version': '0.1', + 'requires_dist': ['bacon (<= 0.2)'], + 'url': archive_path}, + {'name': 'bacon', + 'version': '0.1', + 'requires_dist': [], + 'url': archive_path}, + ]) + installed = get_installed_dists([('bacon', '0.1', [])]) + output = install.get_infos("choxie", index=client, + installed=installed) + + # we don't have installed bacon as it's already installed system-wide + self.assertEqual(0, len(output['remove'])) + self.assertEqual(2, len(output['install'])) + readable_output = [(o.name, str(o.version)) + for o in output['install']] + self.assertIn(('towel-stuff', '0.1'), readable_output) + self.assertIn(('choxie', '2.0.0.9'), readable_output) + + @use_xmlrpc_server() + def test_upgrade_existing_deps(self, server): + client = self._get_client(server) + archive_path = '%s/distribution.tar.gz' % server.full_address + server.xmlrpc.set_distributions([ + {'name': 'choxie', + 'version': '2.0.0.9', + 'requires_dist': ['towel-stuff (0.1)'], + 'url': archive_path}, + {'name': 'towel-stuff', + 'version': '0.1', + 'requires_dist': ['bacon (>= 0.2)'], + 'url': archive_path}, + {'name': 'bacon', + 'version': '0.2', + 'requires_dist': [], + 'url': archive_path}, + ]) + + output = install.get_infos("choxie", index=client, + installed=get_installed_dists([('bacon', '0.1', [])])) + installed = [(o.name, str(o.version)) for o in output['install']] + + # we need bacon 0.2, but 0.1 is installed. + # So we expect to remove 0.1 and to install 0.2 instead. + remove = [(o.name, str(o.version)) for o in output['remove']] + self.assertIn(('choxie', '2.0.0.9'), installed) + self.assertIn(('towel-stuff', '0.1'), installed) + self.assertIn(('bacon', '0.2'), installed) + self.assertIn(('bacon', '0.1'), remove) + self.assertEqual(0, len(output['conflict'])) + + @use_xmlrpc_server() + def test_conflicts(self, server): + # Tests that conflicts are detected + client = self._get_client(server) + archive_path = '%s/distribution.tar.gz' % server.full_address + + # choxie depends on towel-stuff, which depends on bacon. + server.xmlrpc.set_distributions([ + {'name': 'choxie', + 'version': '2.0.0.9', + 'requires_dist': ['towel-stuff (0.1)'], + 'url': archive_path}, + {'name': 'towel-stuff', + 'version': '0.1', + 'requires_dist': ['bacon (>= 0.2)'], + 'url': archive_path}, + {'name': 'bacon', + 'version': '0.2', + 'requires_dist': [], + 'url': archive_path}, + ]) + + # name, version, deps. + already_installed = [('bacon', '0.1', []), + ('chicken', '1.1', ['bacon (0.1)'])] + output = install.get_infos( + 'choxie', index=client, + installed=get_installed_dists(already_installed)) + + # we need bacon 0.2, but 0.1 is installed. + # So we expect to remove 0.1 and to install 0.2 instead. + installed, remove, conflict = self._get_results(output) + self.assertIn(('choxie', '2.0.0.9'), installed) + self.assertIn(('towel-stuff', '0.1'), installed) + self.assertIn(('bacon', '0.2'), installed) + self.assertIn(('bacon', '0.1'), remove) + self.assertIn(('chicken', '1.1'), conflict) + + @use_xmlrpc_server() + def test_installation_unexisting_project(self, server): + # Test that the isntalled raises an exception if the project does not + # exists. + client = self._get_client(server) + self.assertRaises(install.InstallationException, + install.get_infos, + 'unexisting project', index=client) + + def test_move_files(self): + # test that the files are really moved, and that the new path is + # returned. + path = self.mkdtemp() + newpath = self.mkdtemp() + files = [os.path.join(path, str(x)) for x in range(1, 20)] + for f in files: + open(f, 'a+').close() + output = [o for o in install._move_files(files, newpath)] + + # check that output return the list of old/new places + for f in files: + self.assertIn((f, '%s%s' % (newpath, f)), output) + + # remove the files + for f in [o[1] for o in output]: # o[1] is the new place + os.remove(f) + + def test_update_infos(self): + tests = [[ + {'foo': ['foobar', 'foo', 'baz'], 'baz': ['foo', 'foo']}, + {'foo': ['additional_content', 'yeah'], 'baz': ['test', 'foo']}, + {'foo': ['foobar', 'foo', 'baz', 'additional_content', 'yeah'], + 'baz': ['foo', 'foo', 'test', 'foo']}, + ]] + + for dict1, dict2, expect in tests: + install._update_infos(dict1, dict2) + for key in expect: + self.assertEqual(expect[key], dict1[key]) + + def test_install_dists_rollback(self): + # if one of the distribution installation fails, call uninstall on all + # installed distributions. + + old_install_dist = install._install_dist + old_uninstall = getattr(install, 'uninstall', None) + + install._install_dist = MagicMock(return_value=[], + raise_exception=(False, True)) + install.remove = MagicMock() + try: + d1 = ToInstallDist() + d2 = ToInstallDist() + path = self.mkdtemp() + self.assertRaises(Exception, install.install_dists, [d1, d2], path) + self.assertTrue(install._install_dist.called_with(d1, path)) + self.assertTrue(install.remove.called) + finally: + install._install_dist = old_install_dist + install.remove = old_uninstall + + def test_install_dists_success(self): + old_install_dist = install._install_dist + install._install_dist = MagicMock(return_value=[]) + try: + # test that the install method is called on each distributions + d1 = ToInstallDist() + d2 = ToInstallDist() + + # should call install + path = self.mkdtemp() + install.install_dists([d1, d2], path) + for dist in (d1, d2): + self.assertTrue(install._install_dist.called_with(dist, path)) + finally: + install._install_dist = old_install_dist + + def test_install_from_infos_conflict(self): + # assert conflicts raise an exception + self.assertRaises(install.InstallationConflict, + install.install_from_infos, + conflicts=[ToInstallDist()]) + + def test_install_from_infos_remove_success(self): + old_install_dists = install.install_dists + install.install_dists = lambda x, y=None: None + try: + dists = [] + for i in range(2): + dists.append(ToInstallDist(files=True)) + install.install_from_infos(remove=dists) + + # assert that the files have been removed + for dist in dists: + for f in dist.list_installed_files(): + self.assertFalse(os.path.exists(f)) + finally: + install.install_dists = old_install_dists + + def test_install_from_infos_remove_rollback(self): + old_install_dist = install._install_dist + old_uninstall = getattr(install, 'uninstall', None) + + install._install_dist = MagicMock(return_value=[], + raise_exception=(False, True)) + install.uninstall = MagicMock() + try: + # assert that if an error occurs, the removed files are restored. + remove = [] + for i in range(2): + remove.append(ToInstallDist(files=True)) + to_install = [ToInstallDist(), ToInstallDist()] + temp_dir = self.mkdtemp() + + self.assertRaises(Exception, install.install_from_infos, + install_path=temp_dir, install=to_install, + remove=remove) + # assert that the files are in the same place + # assert that the files have been removed + for dist in remove: + for f in dist.list_installed_files(): + self.assertTrue(os.path.exists(f)) + dist._unlink_installed_files() + finally: + install.install_dist = old_install_dist + install.uninstall = old_uninstall + + def test_install_from_infos_install_succes(self): + old_install_dist = install._install_dist + install._install_dist = MagicMock([]) + try: + # assert that the distribution can be installed + install_path = "my_install_path" + to_install = [ToInstallDist(), ToInstallDist()] + + install.install_from_infos(install_path, install=to_install) + for dist in to_install: + install._install_dist.called_with(install_path) + finally: + install._install_dist = old_install_dist + + +def test_suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(TestInstall)) + return suite + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/Lib/packaging/tests/test_manifest.py b/Lib/packaging/tests/test_manifest.py new file mode 100644 index 0000000..21a42c3 --- /dev/null +++ b/Lib/packaging/tests/test_manifest.py @@ -0,0 +1,72 @@ +"""Tests for packaging.manifest.""" +import os +import logging +from io import StringIO +from packaging.manifest import Manifest + +from packaging.tests import unittest, support + +_MANIFEST = """\ +recursive-include foo *.py # ok +# nothing here + +# + +recursive-include bar \\ + *.dat *.txt +""" + +_MANIFEST2 = """\ +README +file1 +""" + + +class ManifestTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + def test_manifest_reader(self): + tmpdir = self.mkdtemp() + MANIFEST = os.path.join(tmpdir, 'MANIFEST.in') + with open(MANIFEST, 'w') as f: + f.write(_MANIFEST) + + manifest = Manifest() + manifest.read_template(MANIFEST) + + warnings = self.get_logs(logging.WARNING) + # the manifest should have been read and 3 warnings issued + # (we didn't provide the files) + self.assertEqual(3, len(warnings)) + for warning in warnings: + self.assertIn('no files found matching', warning) + + # reset logs for the next assert + self.loghandler.flush() + + # manifest also accepts file-like objects + with open(MANIFEST) as f: + manifest.read_template(f) + + # the manifest should have been read and 3 warnings issued + # (we didn't provide the files) + self.assertEqual(3, len(warnings)) + + def test_default_actions(self): + tmpdir = self.mkdtemp() + self.addCleanup(os.chdir, os.getcwd()) + os.chdir(tmpdir) + self.write_file('README', 'xxx') + self.write_file('file1', 'xxx') + content = StringIO(_MANIFEST2) + manifest = Manifest() + manifest.read_template(content) + self.assertEqual(['README', 'file1'], manifest.files) + + +def test_suite(): + return unittest.makeSuite(ManifestTestCase) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/Lib/packaging/tests/test_markers.py b/Lib/packaging/tests/test_markers.py new file mode 100644 index 0000000..dec0429 --- /dev/null +++ b/Lib/packaging/tests/test_markers.py @@ -0,0 +1,71 @@ +"""Tests for packaging.markers.""" +import os +import sys +import platform +from packaging.markers import interpret + +from packaging.tests import unittest +from packaging.tests.support import LoggingCatcher + + +class MarkersTestCase(LoggingCatcher, + unittest.TestCase): + + def test_interpret(self): + sys_platform = sys.platform + version = sys.version.split()[0] + os_name = os.name + platform_version = platform.version() + platform_machine = platform.machine() + platform_python_implementation = platform.python_implementation() + + self.assertTrue(interpret("sys.platform == '%s'" % sys_platform)) + self.assertTrue(interpret( + "sys.platform == '%s' or python_version == '2.4'" % sys_platform)) + self.assertTrue(interpret( + "sys.platform == '%s' and python_full_version == '%s'" % + (sys_platform, version))) + self.assertTrue(interpret("'%s' == sys.platform" % sys_platform)) + self.assertTrue(interpret('os.name == "%s"' % os_name)) + self.assertTrue(interpret( + 'platform.version == "%s" and platform.machine == "%s"' % + (platform_version, platform_machine))) + self.assertTrue(interpret('platform.python_implementation == "%s"' % + platform_python_implementation)) + + # stuff that need to raise a syntax error + ops = ('os.name == os.name', 'os.name == 2', "'2' == '2'", + 'okpjonon', '', 'os.name ==', 'python_version == 2.4') + for op in ops: + self.assertRaises(SyntaxError, interpret, op) + + # combined operations + OP = 'os.name == "%s"' % os_name + AND = ' and ' + OR = ' or ' + self.assertTrue(interpret(OP + AND + OP)) + self.assertTrue(interpret(OP + AND + OP + AND + OP)) + self.assertTrue(interpret(OP + OR + OP)) + self.assertTrue(interpret(OP + OR + OP + OR + OP)) + + # other operators + self.assertTrue(interpret("os.name != 'buuuu'")) + self.assertTrue(interpret("python_version > '1.0'")) + self.assertTrue(interpret("python_version < '5.0'")) + self.assertTrue(interpret("python_version <= '5.0'")) + self.assertTrue(interpret("python_version >= '1.0'")) + self.assertTrue(interpret("'%s' in os.name" % os_name)) + self.assertTrue(interpret("'buuuu' not in os.name")) + self.assertTrue(interpret( + "'buuuu' not in os.name and '%s' in os.name" % os_name)) + + # execution context + self.assertTrue(interpret('python_version == "0.1"', + {'python_version': '0.1'})) + + +def test_suite(): + return unittest.makeSuite(MarkersTestCase) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/Lib/packaging/tests/test_metadata.py b/Lib/packaging/tests/test_metadata.py new file mode 100644 index 0000000..b8dc5d8 --- /dev/null +++ b/Lib/packaging/tests/test_metadata.py @@ -0,0 +1,279 @@ +"""Tests for packaging.metadata.""" +import os +import sys +import logging +from io import StringIO + +from packaging.errors import (MetadataConflictError, MetadataMissingError, + MetadataUnrecognizedVersionError) +from packaging.metadata import Metadata, PKG_INFO_PREFERRED_VERSION + +from packaging.tests import unittest +from packaging.tests.support import LoggingCatcher + + +class MetadataTestCase(LoggingCatcher, + unittest.TestCase): + + def test_instantiation(self): + PKG_INFO = os.path.join(os.path.dirname(__file__), 'PKG-INFO') + with open(PKG_INFO, 'r') as f: + contents = f.read() + fp = StringIO(contents) + + m = Metadata() + self.assertRaises(MetadataUnrecognizedVersionError, m.items) + + m = Metadata(PKG_INFO) + self.assertEqual(len(m.items()), 22) + + m = Metadata(fileobj=fp) + self.assertEqual(len(m.items()), 22) + + m = Metadata(mapping=dict(name='Test', version='1.0')) + self.assertEqual(len(m.items()), 11) + + d = dict(m.items()) + self.assertRaises(TypeError, Metadata, + PKG_INFO, fileobj=fp) + self.assertRaises(TypeError, Metadata, + PKG_INFO, mapping=d) + self.assertRaises(TypeError, Metadata, + fileobj=fp, mapping=d) + self.assertRaises(TypeError, Metadata, + PKG_INFO, mapping=m, fileobj=fp) + + def test_metadata_read_write(self): + PKG_INFO = os.path.join(os.path.dirname(__file__), 'PKG-INFO') + metadata = Metadata(PKG_INFO) + out = StringIO() + metadata.write_file(out) + out.seek(0) + res = Metadata() + res.read_file(out) + for k in metadata: + self.assertEqual(metadata[k], res[k]) + + def test_metadata_markers(self): + # see if we can be platform-aware + PKG_INFO = os.path.join(os.path.dirname(__file__), 'PKG-INFO') + with open(PKG_INFO, 'r') as f: + content = f.read() % sys.platform + metadata = Metadata(platform_dependent=True) + + metadata.read_file(StringIO(content)) + self.assertEqual(metadata['Requires-Dist'], ['bar']) + metadata['Name'] = "baz; sys.platform == 'blah'" + # FIXME is None or 'UNKNOWN' correct here? + # where is that documented? + self.assertEqual(metadata['Name'], None) + + # test with context + context = {'sys.platform': 'okook'} + metadata = Metadata(platform_dependent=True, + execution_context=context) + metadata.read_file(StringIO(content)) + self.assertEqual(metadata['Requires-Dist'], ['foo']) + + def test_description(self): + PKG_INFO = os.path.join(os.path.dirname(__file__), 'PKG-INFO') + with open(PKG_INFO, 'r') as f: + content = f.read() % sys.platform + metadata = Metadata() + metadata.read_file(StringIO(content)) + + # see if we can read the description now + DESC = os.path.join(os.path.dirname(__file__), 'LONG_DESC.txt') + with open(DESC) as f: + wanted = f.read() + self.assertEqual(wanted, metadata['Description']) + + # save the file somewhere and make sure we can read it back + out = StringIO() + metadata.write_file(out) + out.seek(0) + metadata.read_file(out) + self.assertEqual(wanted, metadata['Description']) + + def test_mapping_api(self): + PKG_INFO = os.path.join(os.path.dirname(__file__), 'PKG-INFO') + with open(PKG_INFO, 'r') as f: + content = f.read() % sys.platform + metadata = Metadata(fileobj=StringIO(content)) + self.assertIn('Version', metadata.keys()) + self.assertIn('0.5', metadata.values()) + self.assertIn(('Version', '0.5'), metadata.items()) + + metadata.update({'version': '0.6'}) + self.assertEqual(metadata['Version'], '0.6') + metadata.update([('version', '0.7')]) + self.assertEqual(metadata['Version'], '0.7') + + self.assertEqual(list(metadata), list(metadata.keys())) + + def test_versions(self): + metadata = Metadata() + metadata['Obsoletes'] = 'ok' + self.assertEqual(metadata['Metadata-Version'], '1.1') + + del metadata['Obsoletes'] + metadata['Obsoletes-Dist'] = 'ok' + self.assertEqual(metadata['Metadata-Version'], '1.2') + + self.assertRaises(MetadataConflictError, metadata.set, + 'Obsoletes', 'ok') + + del metadata['Obsoletes'] + del metadata['Obsoletes-Dist'] + metadata['Version'] = '1' + self.assertEqual(metadata['Metadata-Version'], '1.0') + + PKG_INFO = os.path.join(os.path.dirname(__file__), + 'SETUPTOOLS-PKG-INFO') + with open(PKG_INFO, 'r') as f: + content = f.read() + metadata.read_file(StringIO(content)) + self.assertEqual(metadata['Metadata-Version'], '1.0') + + PKG_INFO = os.path.join(os.path.dirname(__file__), + 'SETUPTOOLS-PKG-INFO2') + with open(PKG_INFO, 'r') as f: + content = f.read() + metadata.read_file(StringIO(content)) + self.assertEqual(metadata['Metadata-Version'], '1.1') + + # Update the _fields dict directly to prevent 'Metadata-Version' + # from being updated by the _set_best_version() method. + metadata._fields['Metadata-Version'] = '1.618' + self.assertRaises(MetadataUnrecognizedVersionError, metadata.keys) + + def test_warnings(self): + metadata = Metadata() + + # these should raise a warning + values = (('Requires-Dist', 'Funky (Groovie)'), + ('Requires-Python', '1-4')) + + for name, value in values: + metadata.set(name, value) + + # we should have a certain amount of warnings + self.assertEqual(len(self.get_logs()), 2) + + def test_multiple_predicates(self): + metadata = Metadata() + + # see for "3" instead of "3.0" ??? + # its seems like the MINOR VERSION can be omitted + metadata['Requires-Python'] = '>=2.6, <3.0' + metadata['Requires-Dist'] = ['Foo (>=2.6, <3.0)'] + + self.assertEqual([], self.get_logs(logging.WARNING)) + + def test_project_url(self): + metadata = Metadata() + metadata['Project-URL'] = [('one', 'http://ok')] + self.assertEqual(metadata['Project-URL'], + [('one', 'http://ok')]) + self.assertEqual(metadata['Metadata-Version'], '1.2') + + def test_check_version(self): + metadata = Metadata() + metadata['Name'] = 'vimpdb' + metadata['Home-page'] = 'http://pypi.python.org' + metadata['Author'] = 'Monty Python' + metadata.docutils_support = False + missing, warnings = metadata.check() + self.assertEqual(missing, ['Version']) + + def test_check_version_strict(self): + metadata = Metadata() + metadata['Name'] = 'vimpdb' + metadata['Home-page'] = 'http://pypi.python.org' + metadata['Author'] = 'Monty Python' + metadata.docutils_support = False + self.assertRaises(MetadataMissingError, metadata.check, strict=True) + + def test_check_name(self): + metadata = Metadata() + metadata['Version'] = '1.0' + metadata['Home-page'] = 'http://pypi.python.org' + metadata['Author'] = 'Monty Python' + metadata.docutils_support = False + missing, warnings = metadata.check() + self.assertEqual(missing, ['Name']) + + def test_check_name_strict(self): + metadata = Metadata() + metadata['Version'] = '1.0' + metadata['Home-page'] = 'http://pypi.python.org' + metadata['Author'] = 'Monty Python' + metadata.docutils_support = False + self.assertRaises(MetadataMissingError, metadata.check, strict=True) + + def test_check_author(self): + metadata = Metadata() + metadata['Version'] = '1.0' + metadata['Name'] = 'vimpdb' + metadata['Home-page'] = 'http://pypi.python.org' + metadata.docutils_support = False + missing, warnings = metadata.check() + self.assertEqual(missing, ['Author']) + + def test_check_homepage(self): + metadata = Metadata() + metadata['Version'] = '1.0' + metadata['Name'] = 'vimpdb' + metadata['Author'] = 'Monty Python' + metadata.docutils_support = False + missing, warnings = metadata.check() + self.assertEqual(missing, ['Home-page']) + + def test_check_predicates(self): + metadata = Metadata() + metadata['Version'] = 'rr' + metadata['Name'] = 'vimpdb' + metadata['Home-page'] = 'http://pypi.python.org' + metadata['Author'] = 'Monty Python' + metadata['Requires-dist'] = ['Foo (a)'] + metadata['Obsoletes-dist'] = ['Foo (a)'] + metadata['Provides-dist'] = ['Foo (a)'] + if metadata.docutils_support: + missing, warnings = metadata.check() + self.assertEqual(len(warnings), 4) + metadata.docutils_support = False + missing, warnings = metadata.check() + self.assertEqual(len(warnings), 4) + + def test_best_choice(self): + metadata = Metadata() + metadata['Version'] = '1.0' + self.assertEqual(metadata['Metadata-Version'], + PKG_INFO_PREFERRED_VERSION) + metadata['Classifier'] = ['ok'] + self.assertEqual(metadata['Metadata-Version'], '1.2') + + def test_project_urls(self): + # project-url is a bit specific, make sure we write it + # properly in PKG-INFO + metadata = Metadata() + metadata['Version'] = '1.0' + metadata['Project-Url'] = [('one', 'http://ok')] + self.assertEqual(metadata['Project-Url'], [('one', 'http://ok')]) + file_ = StringIO() + metadata.write_file(file_) + file_.seek(0) + res = file_.read().split('\n') + self.assertIn('Project-URL: one,http://ok', res) + + file_.seek(0) + metadata = Metadata() + metadata.read_file(file_) + self.assertEqual(metadata['Project-Url'], [('one', 'http://ok')]) + + +def test_suite(): + return unittest.makeSuite(MetadataTestCase) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/Lib/packaging/tests/test_mixin2to3.py b/Lib/packaging/tests/test_mixin2to3.py new file mode 100644 index 0000000..d7c83c2 --- /dev/null +++ b/Lib/packaging/tests/test_mixin2to3.py @@ -0,0 +1,75 @@ +"""Tests for packaging.command.build_py.""" +import sys + +from packaging.tests import unittest, support +from packaging.compat import Mixin2to3 + + +class Mixin2to3TestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + @unittest.skipIf(sys.version < '2.6', 'requires Python 2.6 or higher') + def test_convert_code_only(self): + # used to check if code gets converted properly. + code_content = "print 'test'\n" + code_handle = self.mktempfile() + code_name = code_handle.name + + code_handle.write(code_content) + code_handle.flush() + + mixin2to3 = Mixin2to3() + mixin2to3._run_2to3([code_name]) + converted_code_content = "print('test')\n" + with open(code_name) as fp: + new_code_content = "".join(fp.readlines()) + + self.assertEqual(new_code_content, converted_code_content) + + @unittest.skipIf(sys.version < '2.6', 'requires Python 2.6 or higher') + def test_doctests_only(self): + # used to check if doctests gets converted properly. + doctest_content = '"""\n>>> print test\ntest\n"""\nprint test\n\n' + doctest_handle = self.mktempfile() + doctest_name = doctest_handle.name + + doctest_handle.write(doctest_content) + doctest_handle.flush() + + mixin2to3 = Mixin2to3() + mixin2to3._run_2to3([doctest_name]) + + converted_doctest_content = ['"""', '>>> print(test)', 'test', '"""', + 'print(test)', '', '', ''] + converted_doctest_content = '\n'.join(converted_doctest_content) + with open(doctest_name) as fp: + new_doctest_content = "".join(fp.readlines()) + + self.assertEqual(new_doctest_content, converted_doctest_content) + + @unittest.skipIf(sys.version < '2.6', 'requires Python 2.6 or higher') + def test_additional_fixers(self): + # used to check if use_2to3_fixers works + code_content = "type(x) is T" + code_handle = self.mktempfile() + code_name = code_handle.name + + code_handle.write(code_content) + code_handle.flush() + + mixin2to3 = Mixin2to3() + + mixin2to3._run_2to3(files=[code_name], doctests=[code_name], + fixers=['packaging.tests.fixer']) + converted_code_content = "isinstance(x, T)" + with open(code_name) as fp: + new_code_content = "".join(fp.readlines()) + self.assertEqual(new_code_content, converted_code_content) + + +def test_suite(): + return unittest.makeSuite(Mixin2to3TestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_msvc9compiler.py b/Lib/packaging/tests/test_msvc9compiler.py new file mode 100644 index 0000000..dc3ae65 --- /dev/null +++ b/Lib/packaging/tests/test_msvc9compiler.py @@ -0,0 +1,140 @@ +"""Tests for packaging.compiler.msvc9compiler.""" +import os +import sys + +from packaging.errors import PackagingPlatformError + +from packaging.tests import unittest, support + +_MANIFEST = """\ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" + manifestVersion="1.0"> + <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> + <security> + <requestedPrivileges> + <requestedExecutionLevel level="asInvoker" uiAccess="false"> + </requestedExecutionLevel> + </requestedPrivileges> + </security> + </trustInfo> + <dependency> + <dependentAssembly> + <assemblyIdentity type="win32" name="Microsoft.VC90.CRT" + version="9.0.21022.8" processorArchitecture="x86" + publicKeyToken="XXXX"> + </assemblyIdentity> + </dependentAssembly> + </dependency> + <dependency> + <dependentAssembly> + <assemblyIdentity type="win32" name="Microsoft.VC90.MFC" + version="9.0.21022.8" processorArchitecture="x86" + publicKeyToken="XXXX"></assemblyIdentity> + </dependentAssembly> + </dependency> +</assembly> +""" + +_CLEANED_MANIFEST = """\ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" + manifestVersion="1.0"> + <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> + <security> + <requestedPrivileges> + <requestedExecutionLevel level="asInvoker" uiAccess="false"> + </requestedExecutionLevel> + </requestedPrivileges> + </security> + </trustInfo> + <dependency> + + </dependency> + <dependency> + <dependentAssembly> + <assemblyIdentity type="win32" name="Microsoft.VC90.MFC" + version="9.0.21022.8" processorArchitecture="x86" + publicKeyToken="XXXX"></assemblyIdentity> + </dependentAssembly> + </dependency> +</assembly>""" + + +class msvc9compilerTestCase(support.TempdirManager, + unittest.TestCase): + + @unittest.skipUnless(sys.platform == "win32", "runs only on win32") + def test_no_compiler(self): + # make sure query_vcvarsall raises a PackagingPlatformError if + # the compiler is not found + from packaging.compiler.msvccompiler import get_build_version + if get_build_version() < 8.0: + raise unittest.SkipTest('only for MSVC8.0 or above') + + from packaging.compiler import msvc9compiler + from packaging.compiler.msvc9compiler import query_vcvarsall + + def _find_vcvarsall(version): + return None + + old_find_vcvarsall = msvc9compiler.find_vcvarsall + msvc9compiler.find_vcvarsall = _find_vcvarsall + try: + self.assertRaises(PackagingPlatformError, query_vcvarsall, + 'wont find this version') + finally: + msvc9compiler.find_vcvarsall = old_find_vcvarsall + + @unittest.skipUnless(sys.platform == "win32", "runs only on win32") + def test_reg_class(self): + from packaging.compiler.msvccompiler import get_build_version + if get_build_version() < 8.0: + raise unittest.SkipTest("requires MSVC 8.0 or later") + + from packaging.compiler.msvc9compiler import Reg + self.assertRaises(KeyError, Reg.get_value, 'xxx', 'xxx') + + # looking for values that should exist on all + # windows registeries versions. + path = r'Control Panel\Desktop' + v = Reg.get_value(path, 'dragfullwindows') + self.assertIn(v, ('0', '1', '2')) + + import winreg + HKCU = winreg.HKEY_CURRENT_USER + keys = Reg.read_keys(HKCU, 'xxxx') + self.assertEqual(keys, None) + + keys = Reg.read_keys(HKCU, r'Control Panel') + self.assertIn('Desktop', keys) + + @unittest.skipUnless(sys.platform == "win32", "runs only on win32") + def test_remove_visual_c_ref(self): + from packaging.compiler.msvccompiler import get_build_version + if get_build_version() < 8.0: + raise unittest.SkipTest("requires MSVC 8.0 or later") + + from packaging.compiler.msvc9compiler import MSVCCompiler + tempdir = self.mkdtemp() + manifest = os.path.join(tempdir, 'manifest') + with open(manifest, 'w') as f: + f.write(_MANIFEST) + + compiler = MSVCCompiler() + compiler._remove_visual_c_ref(manifest) + + # see what we got + with open(manifest) as f: + # removing trailing spaces + content = '\n'.join(line.rstrip() for line in f.readlines()) + + # makes sure the manifest was properly cleaned + self.assertEqual(content, _CLEANED_MANIFEST) + + +def test_suite(): + return unittest.makeSuite(msvc9compilerTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_pypi_dist.py b/Lib/packaging/tests/test_pypi_dist.py new file mode 100644 index 0000000..b438cb8 --- /dev/null +++ b/Lib/packaging/tests/test_pypi_dist.py @@ -0,0 +1,277 @@ +"""Tests for the packaging.pypi.dist module.""" + +import os +from packaging.version import VersionPredicate +from packaging.pypi.dist import (ReleaseInfo, ReleasesList, DistInfo, + split_archive_name, get_infos_from_url) +from packaging.pypi.errors import HashDoesNotMatch, UnsupportedHashName + +from packaging.tests import unittest +from packaging.tests.support import TempdirManager +from packaging.tests.pypi_server import use_pypi_server + + +def Dist(*args, **kwargs): + # DistInfo takes a release as a first parameter, avoid this in tests. + return DistInfo(None, *args, **kwargs) + + +class TestReleaseInfo(unittest.TestCase): + + def test_instantiation(self): + # Test the DistInfo class provides us the good attributes when + # given on construction + release = ReleaseInfo("FooBar", "1.1") + self.assertEqual("FooBar", release.name) + self.assertEqual("1.1", "%s" % release.version) + + def test_add_dist(self): + # empty distribution type should assume "sdist" + release = ReleaseInfo("FooBar", "1.1") + release.add_distribution(url="http://example.org/") + # should not fail + release['sdist'] + + def test_get_unknown_distribution(self): + # should raise a KeyError + pass + + def test_get_infos_from_url(self): + # Test that the the URLs are parsed the right way + url_list = { + 'FooBar-1.1.0.tar.gz': { + 'name': 'foobar', # lowercase the name + 'version': '1.1.0', + }, + 'Foo-Bar-1.1.0.zip': { + 'name': 'foo-bar', # keep the dash + 'version': '1.1.0', + }, + 'foobar-1.1b2.tar.gz#md5=123123123123123': { + 'name': 'foobar', + 'version': '1.1b2', + 'url': 'http://example.org/foobar-1.1b2.tar.gz', # no hash + 'hashval': '123123123123123', + 'hashname': 'md5', + }, + 'foobar-1.1-rc2.tar.gz': { # use suggested name + 'name': 'foobar', + 'version': '1.1c2', + 'url': 'http://example.org/foobar-1.1-rc2.tar.gz', + } + } + + for url, attributes in url_list.items(): + # for each url + infos = get_infos_from_url("http://example.org/" + url) + for attribute, expected in attributes.items(): + got = infos.get(attribute) + if attribute == "version": + self.assertEqual("%s" % got, expected) + else: + self.assertEqual(got, expected) + + def test_split_archive_name(self): + # Test we can split the archive names + names = { + 'foo-bar-baz-1.0-rc2': ('foo-bar-baz', '1.0c2'), + 'foo-bar-baz-1.0': ('foo-bar-baz', '1.0'), + 'foobarbaz-1.0': ('foobarbaz', '1.0'), + } + for name, results in names.items(): + self.assertEqual(results, split_archive_name(name)) + + +class TestDistInfo(TempdirManager, unittest.TestCase): + srcpath = "/packages/source/f/foobar/foobar-0.1.tar.gz" + + def test_get_url(self): + # Test that the url property works well + + d = Dist(url="test_url") + self.assertDictEqual(d.url, { + "url": "test_url", + "is_external": True, + "hashname": None, + "hashval": None, + }) + + # add a new url + d.add_url(url="internal_url", is_external=False) + self.assertEqual(d._url, None) + self.assertDictEqual(d.url, { + "url": "internal_url", + "is_external": False, + "hashname": None, + "hashval": None, + }) + self.assertEqual(2, len(d.urls)) + + def test_comparison(self): + # Test that we can compare DistInfoributionInfoList + foo1 = ReleaseInfo("foo", "1.0") + foo2 = ReleaseInfo("foo", "2.0") + bar = ReleaseInfo("bar", "2.0") + # assert we use the version to compare + self.assertTrue(foo1 < foo2) + self.assertFalse(foo1 > foo2) + self.assertFalse(foo1 == foo2) + + # assert we can't compare dists with different names + self.assertRaises(TypeError, foo1.__eq__, bar) + + @use_pypi_server("downloads_with_md5") + def test_download(self, server): + # Download is possible, and the md5 is checked if given + + url = server.full_address + self.srcpath + + # check that a md5 if given + dist = Dist(url=url, hashname="md5", + hashval="fe18804c5b722ff024cabdf514924fc4") + dist.download(self.mkdtemp()) + + # a wrong md5 fails + dist2 = Dist(url=url, hashname="md5", hashval="wrongmd5") + + self.assertRaises(HashDoesNotMatch, dist2.download, self.mkdtemp()) + + # we can omit the md5 hash + dist3 = Dist(url=url) + dist3.download(self.mkdtemp()) + + # and specify a temporary location + # for an already downloaded dist + path1 = self.mkdtemp() + dist3.download(path=path1) + # and for a new one + path2_base = self.mkdtemp() + dist4 = Dist(url=url) + path2 = dist4.download(path=path2_base) + self.assertIn(path2_base, path2) + + def test_hashname(self): + # Invalid hashnames raises an exception on assignation + Dist(hashname="md5", hashval="value") + + self.assertRaises(UnsupportedHashName, Dist, + hashname="invalid_hashname", + hashval="value") + + @use_pypi_server('downloads_with_md5') + def test_unpack(self, server): + url = server.full_address + self.srcpath + dist1 = Dist(url=url) + + # unpack the distribution in a specfied folder + dist1_here = self.mkdtemp() + dist1_there = dist1.unpack(path=dist1_here) + + # assert we unpack to the path provided + self.assertEqual(dist1_here, dist1_there) + dist1_result = os.listdir(dist1_there) + self.assertIn('paf', dist1_result) + os.remove(os.path.join(dist1_there, 'paf')) + + # Test unpack works without a path argument + dist2 = Dist(url=url) + # doing an unpack + dist2_there = dist2.unpack() + dist2_result = os.listdir(dist2_there) + self.assertIn('paf', dist2_result) + os.remove(os.path.join(dist2_there, 'paf')) + + +class TestReleasesList(unittest.TestCase): + + def test_filter(self): + # Test we filter the distributions the right way, using version + # predicate match method + releases = ReleasesList('FooBar', ( + ReleaseInfo("FooBar", "1.1"), + ReleaseInfo("FooBar", "1.1.1"), + ReleaseInfo("FooBar", "1.2"), + ReleaseInfo("FooBar", "1.2.1"), + )) + filtered = releases.filter(VersionPredicate("FooBar (<1.2)")) + self.assertNotIn(releases[2], filtered) + self.assertNotIn(releases[3], filtered) + self.assertIn(releases[0], filtered) + self.assertIn(releases[1], filtered) + + def test_append(self): + # When adding a new item to the list, the behavior is to test if + # a release with the same name and version number already exists, + # and if so, to add a new distribution for it. If the distribution type + # is already defined too, add url informations to the existing DistInfo + # object. + + releases = ReleasesList("FooBar", [ + ReleaseInfo("FooBar", "1.1", url="external_url", + dist_type="sdist"), + ]) + self.assertEqual(1, len(releases)) + releases.add_release(release=ReleaseInfo("FooBar", "1.1", + url="internal_url", + is_external=False, + dist_type="sdist")) + self.assertEqual(1, len(releases)) + self.assertEqual(2, len(releases[0]['sdist'].urls)) + + releases.add_release(release=ReleaseInfo("FooBar", "1.1.1", + dist_type="sdist")) + self.assertEqual(2, len(releases)) + + # when adding a distribution whith a different type, a new distribution + # has to be added. + releases.add_release(release=ReleaseInfo("FooBar", "1.1.1", + dist_type="bdist")) + self.assertEqual(2, len(releases)) + self.assertEqual(2, len(releases[1].dists)) + + def test_prefer_final(self): + # Can order the distributions using prefer_final + + fb10 = ReleaseInfo("FooBar", "1.0") # final distribution + fb11a = ReleaseInfo("FooBar", "1.1a1") # alpha + fb12a = ReleaseInfo("FooBar", "1.2a1") # alpha + fb12b = ReleaseInfo("FooBar", "1.2b1") # beta + dists = ReleasesList("FooBar", [fb10, fb11a, fb12a, fb12b]) + + dists.sort_releases(prefer_final=True) + self.assertEqual(fb10, dists[0]) + + dists.sort_releases(prefer_final=False) + self.assertEqual(fb12b, dists[0]) + +# def test_prefer_source(self): +# # Ordering support prefer_source +# fb_source = Dist("FooBar", "1.0", type="source") +# fb_binary = Dist("FooBar", "1.0", type="binary") +# fb2_binary = Dist("FooBar", "2.0", type="binary") +# dists = ReleasesList([fb_binary, fb_source]) +# +# dists.sort_distributions(prefer_source=True) +# self.assertEqual(fb_source, dists[0]) +# +# dists.sort_distributions(prefer_source=False) +# self.assertEqual(fb_binary, dists[0]) +# +# dists.append(fb2_binary) +# dists.sort_distributions(prefer_source=True) +# self.assertEqual(fb2_binary, dists[0]) + + def test_get_last(self): + dists = ReleasesList('Foo') + self.assertEqual(dists.get_last('Foo 1.0'), None) + + +def test_suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(TestDistInfo)) + suite.addTest(unittest.makeSuite(TestReleaseInfo)) + suite.addTest(unittest.makeSuite(TestReleasesList)) + return suite + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/Lib/packaging/tests/test_pypi_server.py b/Lib/packaging/tests/test_pypi_server.py new file mode 100644 index 0000000..15c2e6c --- /dev/null +++ b/Lib/packaging/tests/test_pypi_server.py @@ -0,0 +1,81 @@ +"""Tests for packaging.command.bdist.""" +import sys + +import urllib.request +import urllib.parse +import urllib.error + +from packaging.tests.pypi_server import PyPIServer, PYPI_DEFAULT_STATIC_PATH +from packaging.tests import unittest + + +class PyPIServerTest(unittest.TestCase): + + def test_records_requests(self): + # We expect that PyPIServer can log our requests + server = PyPIServer() + server.default_response_status = 200 + + try: + server.start() + self.assertEqual(len(server.requests), 0) + + data = b'Rock Around The Bunker' + + headers = {"X-test-header": "Mister Iceberg"} + + request = urllib.request.Request(server.full_address, data, headers) + urllib.request.urlopen(request) + self.assertEqual(len(server.requests), 1) + handler, request_data = server.requests[-1] + self.assertIn(data, request_data) + self.assertIn("x-test-header", handler.headers) + self.assertEqual(handler.headers["x-test-header"], "Mister Iceberg") + + finally: + server.stop() + + + def test_serve_static_content(self): + # PYPI Mocked server can serve static content from disk. + + def uses_local_files_for(server, url_path): + """Test that files are served statically (eg. the output from the + server is the same than the one made by a simple file read. + """ + url = server.full_address + url_path + request = urllib.request.Request(url) + response = urllib.request.urlopen(request) + file = open(PYPI_DEFAULT_STATIC_PATH + "/test_pypi_server" + + url_path) + answer = response.read().decode() == file.read() + file.close() + return answer + + server = PyPIServer(static_uri_paths=["simple", "external"], + static_filesystem_paths=["test_pypi_server"]) + server.start() + try: + # the file does not exists on the disc, so it might not be served + url = server.full_address + "/simple/unexisting_page" + request = urllib.request.Request(url) + try: + urllib.request.urlopen(request) + except urllib.error.HTTPError as e: + self.assertEqual(e.code, 404) + + # now try serving a content that do exists + self.assertTrue(uses_local_files_for(server, "/simple/index.html")) + + # and another one in another root path + self.assertTrue(uses_local_files_for(server, "/external/index.html")) + + finally: + server.stop() + + +def test_suite(): + return unittest.makeSuite(PyPIServerTest) + +if __name__ == '__main__': + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_pypi_simple.py b/Lib/packaging/tests/test_pypi_simple.py new file mode 100644 index 0000000..c43a839 --- /dev/null +++ b/Lib/packaging/tests/test_pypi_simple.py @@ -0,0 +1,326 @@ +"""Tests for the packaging.pypi.simple module.""" + +import os +import sys +import http.client +import urllib.error +import urllib.parse +import urllib.request + +from packaging.pypi.simple import Crawler + +from packaging.tests import unittest +from packaging.tests.support import TempdirManager, LoggingCatcher +from packaging.tests.pypi_server import (use_pypi_server, PyPIServer, + PYPI_DEFAULT_STATIC_PATH) + + +class SimpleCrawlerTestCase(TempdirManager, + LoggingCatcher, + unittest.TestCase): + + def _get_simple_crawler(self, server, base_url="/simple/", hosts=None, + *args, **kwargs): + """Build and return a SimpleIndex with the test server urls""" + if hosts is None: + hosts = (server.full_address.replace("http://", ""),) + kwargs['hosts'] = hosts + return Crawler(server.full_address + base_url, *args, + **kwargs) + + @use_pypi_server() + def test_bad_urls(self, server): + crawler = Crawler() + url = 'http://127.0.0.1:0/nonesuch/test_simple' + try: + v = crawler._open_url(url) + except Exception as v: + self.assertIn(url, str(v)) + else: + v.close() + self.assertIsInstance(v, urllib.error.HTTPError) + + # issue 16 + # easy_install inquant.contentmirror.plone breaks because of a typo + # in its home URL + crawler = Crawler(hosts=('example.org',)) + url = ('url:%20https://svn.plone.org/svn/collective/' + 'inquant.contentmirror.plone/trunk') + try: + v = crawler._open_url(url) + except Exception as v: + self.assertIn(url, str(v)) + else: + v.close() + self.assertIsInstance(v, urllib.error.HTTPError) + + def _urlopen(*args): + raise http.client.BadStatusLine('line') + + old_urlopen = urllib.request.urlopen + urllib.request.urlopen = _urlopen + url = 'http://example.org' + try: + v = crawler._open_url(url) + except Exception as v: + self.assertIn('line', str(v)) + else: + v.close() + # TODO use self.assertRaises + raise AssertionError('Should have raise here!') + finally: + urllib.request.urlopen = old_urlopen + + # issue 20 + url = 'http://http://svn.pythonpaste.org/Paste/wphp/trunk' + try: + crawler._open_url(url) + except Exception as v: + self.assertIn('nonnumeric port', str(v)) + + # issue #160 + url = server.full_address + page = ('<a href="http://www.famfamfam.com](' + 'http://www.famfamfam.com/">') + crawler._process_url(url, page) + + @use_pypi_server("test_found_links") + def test_found_links(self, server): + # Browse the index, asking for a specified release version + # The PyPI index contains links for version 1.0, 1.1, 2.0 and 2.0.1 + crawler = self._get_simple_crawler(server) + last_release = crawler.get_release("foobar") + + # we have scanned the index page + self.assertIn(server.full_address + "/simple/foobar/", + crawler._processed_urls) + + # we have found 4 releases in this page + self.assertEqual(len(crawler._projects["foobar"]), 4) + + # and returned the most recent one + self.assertEqual("%s" % last_release.version, '2.0.1') + + def test_is_browsable(self): + crawler = Crawler(follow_externals=False) + self.assertTrue(crawler._is_browsable(crawler.index_url + "test")) + + # Now, when following externals, we can have a list of hosts to trust. + # and don't follow other external links than the one described here. + crawler = Crawler(hosts=["pypi.python.org", "example.org"], + follow_externals=True) + good_urls = ( + "http://pypi.python.org/foo/bar", + "http://pypi.python.org/simple/foobar", + "http://example.org", + "http://example.org/", + "http://example.org/simple/", + ) + bad_urls = ( + "http://python.org", + "http://example.tld", + ) + + for url in good_urls: + self.assertTrue(crawler._is_browsable(url)) + + for url in bad_urls: + self.assertFalse(crawler._is_browsable(url)) + + # allow all hosts + crawler = Crawler(follow_externals=True, hosts=("*",)) + self.assertTrue(crawler._is_browsable("http://an-external.link/path")) + self.assertTrue(crawler._is_browsable("pypi.example.org/a/path")) + + # specify a list of hosts we want to allow + crawler = Crawler(follow_externals=True, + hosts=("*.example.org",)) + self.assertFalse(crawler._is_browsable("http://an-external.link/path")) + self.assertTrue( + crawler._is_browsable("http://pypi.example.org/a/path")) + + @use_pypi_server("with_externals") + def test_follow_externals(self, server): + # Include external pages + # Try to request the package index, wich contains links to "externals" + # resources. They have to be scanned too. + crawler = self._get_simple_crawler(server, follow_externals=True) + crawler.get_release("foobar") + self.assertIn(server.full_address + "/external/external.html", + crawler._processed_urls) + + @use_pypi_server("with_real_externals") + def test_restrict_hosts(self, server): + # Only use a list of allowed hosts is possible + # Test that telling the simple pyPI client to not retrieve external + # works + crawler = self._get_simple_crawler(server, follow_externals=False) + crawler.get_release("foobar") + self.assertNotIn(server.full_address + "/external/external.html", + crawler._processed_urls) + + @use_pypi_server(static_filesystem_paths=["with_externals"], + static_uri_paths=["simple", "external"]) + def test_links_priority(self, server): + # Download links from the pypi simple index should be used before + # external download links. + # http://bitbucket.org/tarek/distribute/issue/163/md5-validation-error + # + # Usecase : + # - someone uploads a package on pypi, a md5 is generated + # - someone manually coindexes this link (with the md5 in the url) onto + # an external page accessible from the package page. + # - someone reuploads the package (with a different md5) + # - while easy_installing, an MD5 error occurs because the external + # link is used + # -> The index should use the link from pypi, not the external one. + + # start an index server + index_url = server.full_address + '/simple/' + + # scan a test index + crawler = Crawler(index_url, follow_externals=True) + releases = crawler.get_releases("foobar") + server.stop() + + # we have only one link, because links are compared without md5 + self.assertEqual(1, len(releases)) + self.assertEqual(1, len(releases[0].dists)) + # the link should be from the index + self.assertEqual(2, len(releases[0].dists['sdist'].urls)) + self.assertEqual('12345678901234567', + releases[0].dists['sdist'].url['hashval']) + self.assertEqual('md5', releases[0].dists['sdist'].url['hashname']) + + @use_pypi_server(static_filesystem_paths=["with_norel_links"], + static_uri_paths=["simple", "external"]) + def test_not_scan_all_links(self, server): + # Do not follow all index page links. + # The links not tagged with rel="download" and rel="homepage" have + # to not be processed by the package index, while processing "pages". + + # process the pages + crawler = self._get_simple_crawler(server, follow_externals=True) + crawler.get_releases("foobar") + # now it should have processed only pages with links rel="download" + # and rel="homepage" + self.assertIn("%s/simple/foobar/" % server.full_address, + crawler._processed_urls) # it's the simple index page + self.assertIn("%s/external/homepage.html" % server.full_address, + crawler._processed_urls) # the external homepage is rel="homepage" + self.assertNotIn("%s/external/nonrel.html" % server.full_address, + crawler._processed_urls) # this link contains no rel=* + self.assertNotIn("%s/unrelated-0.2.tar.gz" % server.full_address, + crawler._processed_urls) # linked from simple index (no rel) + self.assertIn("%s/foobar-0.1.tar.gz" % server.full_address, + crawler._processed_urls) # linked from simple index (rel) + self.assertIn("%s/foobar-2.0.tar.gz" % server.full_address, + crawler._processed_urls) # linked from external homepage (rel) + + def test_uses_mirrors(self): + # When the main repository seems down, try using the given mirrors""" + server = PyPIServer("foo_bar_baz") + mirror = PyPIServer("foo_bar_baz") + mirror.start() # we dont start the server here + + try: + # create the index using both servers + crawler = Crawler(server.full_address + "/simple/", hosts=('*',), + # set the timeout to 1s for the tests + timeout=1, mirrors=[mirror.full_address]) + + # this should not raise a timeout + self.assertEqual(4, len(crawler.get_releases("foo"))) + finally: + mirror.stop() + + def test_simple_link_matcher(self): + # Test that the simple link matcher finds the right links""" + crawler = Crawler(follow_externals=False) + + # Here, we define: + # 1. one link that must be followed, cause it's a download one + # 2. one link that must *not* be followed, cause the is_browsable + # returns false for it. + # 3. one link that must be followed cause it's a homepage that is + # browsable + # 4. one link that must be followed, because it contain a md5 hash + self.assertTrue(crawler._is_browsable("%stest" % crawler.index_url)) + self.assertFalse(crawler._is_browsable("http://dl-link2")) + content = """ + <a href="http://dl-link1" rel="download">download_link1</a> + <a href="http://dl-link2" rel="homepage">homepage_link1</a> + <a href="%(index_url)stest" rel="homepage">homepage_link2</a> + <a href="%(index_url)stest/foobar-1.tar.gz#md5=abcdef>download_link2</a> + """ % {'index_url': crawler.index_url} + + # Test that the simple link matcher yield the good links. + generator = crawler._simple_link_matcher(content, crawler.index_url) + self.assertEqual(('%stest/foobar-1.tar.gz#md5=abcdef' % + crawler.index_url, True), next(generator)) + self.assertEqual(('http://dl-link1', True), next(generator)) + self.assertEqual(('%stest' % crawler.index_url, False), + next(generator)) + self.assertRaises(StopIteration, generator.__next__) + + # Follow the external links is possible (eg. homepages) + crawler.follow_externals = True + generator = crawler._simple_link_matcher(content, crawler.index_url) + self.assertEqual(('%stest/foobar-1.tar.gz#md5=abcdef' % + crawler.index_url, True), next(generator)) + self.assertEqual(('http://dl-link1', True), next(generator)) + self.assertEqual(('http://dl-link2', False), next(generator)) + self.assertEqual(('%stest' % crawler.index_url, False), + next(generator)) + self.assertRaises(StopIteration, generator.__next__) + + def test_browse_local_files(self): + # Test that we can browse local files""" + index_path = os.sep.join(["file://" + PYPI_DEFAULT_STATIC_PATH, + "test_found_links", "simple"]) + crawler = Crawler(index_path) + dists = crawler.get_releases("foobar") + self.assertEqual(4, len(dists)) + + def test_get_link_matcher(self): + crawler = Crawler("http://example.org") + self.assertEqual('_simple_link_matcher', crawler._get_link_matcher( + "http://example.org/some/file").__name__) + self.assertEqual('_default_link_matcher', crawler._get_link_matcher( + "http://other-url").__name__) + + def test_default_link_matcher(self): + crawler = Crawler("http://example.org", mirrors=[]) + crawler.follow_externals = True + crawler._is_browsable = lambda *args: True + base_url = "http://example.org/some/file/" + content = """ +<a href="../homepage" rel="homepage">link</a> +<a href="../download" rel="download">link2</a> +<a href="../simpleurl">link2</a> + """ + found_links = set(uri for uri, _ in + crawler._default_link_matcher(content, base_url)) + self.assertIn('http://example.org/some/homepage', found_links) + self.assertIn('http://example.org/some/simpleurl', found_links) + self.assertIn('http://example.org/some/download', found_links) + + @use_pypi_server("project_list") + def test_search_projects(self, server): + # we can search the index for some projects, on their names + # the case used no matters here + crawler = self._get_simple_crawler(server) + tests = (('Foobar', ['FooBar-bar', 'Foobar-baz', 'Baz-FooBar']), + ('foobar*', ['FooBar-bar', 'Foobar-baz']), + ('*foobar', ['Baz-FooBar'])) + + for search, expected in tests: + projects = [p.name for p in crawler.search_projects(search)] + self.assertListEqual(expected, projects) + + +def test_suite(): + return unittest.makeSuite(SimpleCrawlerTestCase) + +if __name__ == '__main__': + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_pypi_xmlrpc.py b/Lib/packaging/tests/test_pypi_xmlrpc.py new file mode 100644 index 0000000..e27c7b3 --- /dev/null +++ b/Lib/packaging/tests/test_pypi_xmlrpc.py @@ -0,0 +1,93 @@ +"""Tests for the packaging.pypi.xmlrpc module.""" + +from packaging.pypi.xmlrpc import Client, InvalidSearchField, ProjectNotFound + +from packaging.tests import unittest +from packaging.tests.pypi_server import use_xmlrpc_server + + +class TestXMLRPCClient(unittest.TestCase): + def _get_client(self, server, *args, **kwargs): + return Client(server.full_address, *args, **kwargs) + + @use_xmlrpc_server() + def test_search_projects(self, server): + client = self._get_client(server) + server.xmlrpc.set_search_result(['FooBar', 'Foo', 'FooFoo']) + results = [r.name for r in client.search_projects(name='Foo')] + self.assertEqual(3, len(results)) + self.assertIn('FooBar', results) + self.assertIn('Foo', results) + self.assertIn('FooFoo', results) + + def test_search_projects_bad_fields(self): + client = Client() + self.assertRaises(InvalidSearchField, client.search_projects, + invalid="test") + + @use_xmlrpc_server() + def test_get_releases(self, server): + client = self._get_client(server) + server.xmlrpc.set_distributions([ + {'name': 'FooBar', 'version': '1.1'}, + {'name': 'FooBar', 'version': '1.2', 'url': 'http://some/url/'}, + {'name': 'FooBar', 'version': '1.3', 'url': 'http://other/url/'}, + ]) + + # use a lambda here to avoid an useless mock call + server.xmlrpc.list_releases = lambda *a, **k: ['1.1', '1.2', '1.3'] + + releases = client.get_releases('FooBar (<=1.2)') + # dont call release_data and release_url; just return name and version. + self.assertEqual(2, len(releases)) + versions = releases.get_versions() + self.assertIn('1.1', versions) + self.assertIn('1.2', versions) + self.assertNotIn('1.3', versions) + + self.assertRaises(ProjectNotFound, client.get_releases, 'Foo') + + @use_xmlrpc_server() + def test_get_distributions(self, server): + client = self._get_client(server) + server.xmlrpc.set_distributions([ + {'name': 'FooBar', 'version': '1.1', + 'url': 'http://example.org/foobar-1.1-sdist.tar.gz', + 'digest': '1234567', + 'type': 'sdist', 'python_version': 'source'}, + {'name':'FooBar', 'version': '1.1', + 'url': 'http://example.org/foobar-1.1-bdist.tar.gz', + 'digest': '8912345', 'type': 'bdist'}, + ]) + + releases = client.get_releases('FooBar', '1.1') + client.get_distributions('FooBar', '1.1') + release = releases.get_release('1.1') + self.assertTrue('http://example.org/foobar-1.1-sdist.tar.gz', + release['sdist'].url['url']) + self.assertTrue('http://example.org/foobar-1.1-bdist.tar.gz', + release['bdist'].url['url']) + self.assertEqual(release['sdist'].python_version, 'source') + + @use_xmlrpc_server() + def test_get_metadata(self, server): + client = self._get_client(server) + server.xmlrpc.set_distributions([ + {'name': 'FooBar', + 'version': '1.1', + 'keywords': '', + 'obsoletes_dist': ['FooFoo'], + 'requires_external': ['Foo'], + }]) + release = client.get_metadata('FooBar', '1.1') + self.assertEqual(['Foo'], release.metadata['requires_external']) + self.assertEqual(['FooFoo'], release.metadata['obsoletes_dist']) + + +def test_suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(TestXMLRPCClient)) + return suite + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/Lib/packaging/tests/test_resources.py b/Lib/packaging/tests/test_resources.py new file mode 100644 index 0000000..158af36 --- /dev/null +++ b/Lib/packaging/tests/test_resources.py @@ -0,0 +1,168 @@ +"""Tests for packaging.resources.""" + +import os +import sys +import shutil +import tempfile +from textwrap import dedent +from packaging.config import get_resources_dests +from packaging.database import disable_cache, enable_cache +from packaging.resources import get_file, get_file_path + +from packaging.tests import unittest +from packaging.tests.test_util import GlobTestCaseBase + + +class DataFilesTestCase(GlobTestCaseBase): + + def assertRulesMatch(self, rules, spec): + tempdir = self.build_files_tree(spec) + expected = self.clean_tree(spec) + result = get_resources_dests(tempdir, rules) + self.assertEqual(expected, result) + + def clean_tree(self, spec): + files = {} + for path, value in spec.items(): + if value is not None: + path = self.os_dependent_path(path) + files[path] = value + return files + + def test_simple_glob(self): + rules = [('', '*.tpl', '{data}')] + spec = {'coucou.tpl': '{data}/coucou.tpl', + 'Donotwant': None} + self.assertRulesMatch(rules, spec) + + def test_multiple_match(self): + rules = [('scripts', '*.bin', '{appdata}'), + ('scripts', '*', '{appscript}')] + spec = {'scripts/script.bin': '{appscript}/script.bin', + 'Babarlikestrawberry': None} + self.assertRulesMatch(rules, spec) + + def test_set_match(self): + rules = [('scripts', '*.{bin,sh}', '{appscript}')] + spec = {'scripts/script.bin': '{appscript}/script.bin', + 'scripts/babar.sh': '{appscript}/babar.sh', + 'Babarlikestrawberry': None} + self.assertRulesMatch(rules, spec) + + def test_set_match_multiple(self): + rules = [('scripts', 'script{s,}.{bin,sh}', '{appscript}')] + spec = {'scripts/scripts.bin': '{appscript}/scripts.bin', + 'scripts/script.sh': '{appscript}/script.sh', + 'Babarlikestrawberry': None} + self.assertRulesMatch(rules, spec) + + def test_set_match_exclude(self): + rules = [('scripts', '*', '{appscript}'), + ('', '**/*.sh', None)] + spec = {'scripts/scripts.bin': '{appscript}/scripts.bin', + 'scripts/script.sh': None, + 'Babarlikestrawberry': None} + self.assertRulesMatch(rules, spec) + + def test_glob_in_base(self): + rules = [('scrip*', '*.bin', '{appscript}')] + spec = {'scripts/scripts.bin': '{appscript}/scripts.bin', + 'scripouille/babar.bin': '{appscript}/babar.bin', + 'scriptortu/lotus.bin': '{appscript}/lotus.bin', + 'Babarlikestrawberry': None} + self.assertRulesMatch(rules, spec) + + def test_recursive_glob(self): + rules = [('', '**/*.bin', '{binary}')] + spec = {'binary0.bin': '{binary}/binary0.bin', + 'scripts/binary1.bin': '{binary}/scripts/binary1.bin', + 'scripts/bin/binary2.bin': '{binary}/scripts/bin/binary2.bin', + 'you/kill/pandabear.guy': None} + self.assertRulesMatch(rules, spec) + + def test_final_exemple_glob(self): + rules = [ + ('mailman/database/schemas/', '*', '{appdata}/schemas'), + ('', '**/*.tpl', '{appdata}/templates'), + ('', 'developer-docs/**/*.txt', '{doc}'), + ('', 'README', '{doc}'), + ('mailman/etc/', '*', '{config}'), + ('mailman/foo/', '**/bar/*.cfg', '{config}/baz'), + ('mailman/foo/', '**/*.cfg', '{config}/hmm'), + ('', 'some-new-semantic.sns', '{funky-crazy-category}'), + ] + spec = { + 'README': '{doc}/README', + 'some.tpl': '{appdata}/templates/some.tpl', + 'some-new-semantic.sns': + '{funky-crazy-category}/some-new-semantic.sns', + 'mailman/database/mailman.db': None, + 'mailman/database/schemas/blah.schema': + '{appdata}/schemas/blah.schema', + 'mailman/etc/my.cnf': '{config}/my.cnf', + 'mailman/foo/some/path/bar/my.cfg': + '{config}/hmm/some/path/bar/my.cfg', + 'mailman/foo/some/path/other.cfg': + '{config}/hmm/some/path/other.cfg', + 'developer-docs/index.txt': '{doc}/developer-docs/index.txt', + 'developer-docs/api/toc.txt': '{doc}/developer-docs/api/toc.txt', + } + self.maxDiff = None + self.assertRulesMatch(rules, spec) + + def test_get_file(self): + # Create a fake dist + temp_site_packages = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, temp_site_packages) + + dist_name = 'test' + dist_info = os.path.join(temp_site_packages, 'test-0.1.dist-info') + os.mkdir(dist_info) + + metadata_path = os.path.join(dist_info, 'METADATA') + resources_path = os.path.join(dist_info, 'RESOURCES') + + with open(metadata_path, 'w') as fp: + fp.write(dedent("""\ + Metadata-Version: 1.2 + Name: test + Version: 0.1 + Summary: test + Author: me + """)) + + test_path = 'test.cfg' + + fd, test_resource_path = tempfile.mkstemp() + os.close(fd) + self.addCleanup(os.remove, test_resource_path) + + with open(test_resource_path, 'w') as fp: + fp.write('Config') + + with open(resources_path, 'w') as fp: + fp.write('%s,%s' % (test_path, test_resource_path)) + + # Add fake site-packages to sys.path to retrieve fake dist + self.addCleanup(sys.path.remove, temp_site_packages) + sys.path.insert(0, temp_site_packages) + + # Force packaging.database to rescan the sys.path + self.addCleanup(enable_cache) + disable_cache() + + # Try to retrieve resources paths and files + self.assertEqual(get_file_path(dist_name, test_path), + test_resource_path) + self.assertRaises(KeyError, get_file_path, dist_name, 'i-dont-exist') + + with get_file(dist_name, test_path) as fp: + self.assertEqual(fp.read(), 'Config') + self.assertRaises(KeyError, get_file, dist_name, 'i-dont-exist') + + +def test_suite(): + return unittest.makeSuite(DataFilesTestCase) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/Lib/packaging/tests/test_run.py b/Lib/packaging/tests/test_run.py new file mode 100644 index 0000000..01fa5aa --- /dev/null +++ b/Lib/packaging/tests/test_run.py @@ -0,0 +1,62 @@ +"""Tests for packaging.run.""" + +import os +import sys +import shutil + +from packaging.tests import unittest, support, TESTFN + +# setup script that uses __file__ +setup_using___file__ = """\ + +__file__ + +from packaging.run import setup +setup() +""" + +setup_prints_cwd = """\ + +import os +print os.getcwd() + +from packaging.run import setup +setup() +""" + + +class CoreTestCase(unittest.TestCase): + + def setUp(self): + super(CoreTestCase, self).setUp() + self.old_stdout = sys.stdout + self.cleanup_testfn() + self.old_argv = sys.argv, sys.argv[:] + + def tearDown(self): + sys.stdout = self.old_stdout + self.cleanup_testfn() + sys.argv = self.old_argv[0] + sys.argv[:] = self.old_argv[1] + super(CoreTestCase, self).tearDown() + + def cleanup_testfn(self): + path = TESTFN + if os.path.isfile(path): + os.remove(path) + elif os.path.isdir(path): + shutil.rmtree(path) + + def write_setup(self, text, path=TESTFN): + with open(path, "w") as fp: + fp.write(text) + return path + + # TODO restore the tests removed six months ago and port them to pysetup + + +def test_suite(): + return unittest.makeSuite(CoreTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_uninstall.py b/Lib/packaging/tests/test_uninstall.py new file mode 100644 index 0000000..ce345b6 --- /dev/null +++ b/Lib/packaging/tests/test_uninstall.py @@ -0,0 +1,99 @@ +"""Tests for the uninstall command.""" +import os +import sys + +from packaging.database import disable_cache, enable_cache +from packaging.run import main +from packaging.errors import PackagingError +from packaging.install import remove +from packaging.command.install_dist import install_dist + +from packaging.tests import unittest, support + +SETUP_CFG = """ +[metadata] +name = %(name)s +version = %(version)s + +[files] +packages = + %(pkg)s + %(pkg)s.sub +""" + + +class UninstallTestCase(support.TempdirManager, + support.LoggingCatcher, + support.EnvironRestorer, + unittest.TestCase): + + restore_environ = ['PLAT'] + + def setUp(self): + super(UninstallTestCase, self).setUp() + self.addCleanup(setattr, sys, 'stdout', sys.stdout) + self.addCleanup(setattr, sys, 'stderr', sys.stderr) + self.addCleanup(os.chdir, os.getcwd()) + self.addCleanup(enable_cache) + self.root_dir = self.mkdtemp() + disable_cache() + + def run_setup(self, *args): + # run setup with args + args = ['run'] + list(args) + dist = main(args) + return dist + + def get_path(self, dist, name): + cmd = install_dist(dist) + cmd.prefix = self.root_dir + cmd.finalize_options() + return getattr(cmd, 'install_' + name) + + def make_dist(self, name='Foo', **kw): + kw['name'] = name + pkg = name.lower() + if 'version' not in kw: + kw['version'] = '0.1' + project_dir, dist = self.create_dist(**kw) + kw['pkg'] = pkg + + pkg_dir = os.path.join(project_dir, pkg) + os.mkdir(pkg_dir) + os.mkdir(os.path.join(pkg_dir, 'sub')) + + self.write_file((project_dir, 'setup.cfg'), SETUP_CFG % kw) + self.write_file((pkg_dir, '__init__.py'), '#') + self.write_file((pkg_dir, pkg + '_utils.py'), '#') + self.write_file((pkg_dir, 'sub', '__init__.py'), '#') + self.write_file((pkg_dir, 'sub', pkg + '_utils.py'), '#') + + return project_dir + + def install_dist(self, name='Foo', dirname=None, **kw): + if not dirname: + dirname = self.make_dist(name, **kw) + os.chdir(dirname) + dist = self.run_setup('install_dist', '--prefix=' + self.root_dir) + install_lib = self.get_path(dist, 'purelib') + return dist, install_lib + + def test_uninstall_unknow_distribution(self): + self.assertRaises(PackagingError, remove, 'Foo', + paths=[self.root_dir]) + + def test_uninstall(self): + dist, install_lib = self.install_dist() + self.assertIsFile(install_lib, 'foo', '__init__.py') + self.assertIsFile(install_lib, 'foo', 'sub', '__init__.py') + self.assertIsFile(install_lib, 'Foo-0.1.dist-info', 'RECORD') + remove('Foo', paths=[install_lib]) + self.assertIsNotFile(install_lib, 'foo', 'sub', '__init__.py') + self.assertIsNotFile(install_lib, 'Foo-0.1.dist-info', 'RECORD') + + +def test_suite(): + return unittest.makeSuite(UninstallTestCase) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/Lib/packaging/tests/test_unixccompiler.py b/Lib/packaging/tests/test_unixccompiler.py new file mode 100644 index 0000000..16a1af3 --- /dev/null +++ b/Lib/packaging/tests/test_unixccompiler.py @@ -0,0 +1,132 @@ +"""Tests for packaging.unixccompiler.""" +import sys + +import sysconfig +from packaging.compiler.unixccompiler import UnixCCompiler +from packaging.tests import unittest + + +class UnixCCompilerTestCase(unittest.TestCase): + + def setUp(self): + self._backup_platform = sys.platform + self._backup_get_config_var = sysconfig.get_config_var + + class CompilerWrapper(UnixCCompiler): + def rpath_foo(self): + return self.runtime_library_dir_option('/foo') + self.cc = CompilerWrapper() + + def tearDown(self): + sys.platform = self._backup_platform + sysconfig.get_config_var = self._backup_get_config_var + + @unittest.skipIf(sys.platform == 'win32', 'irrelevant on win32') + def test_runtime_libdir_option(self): + + # Issue #5900: Ensure RUNPATH is added to extension + # modules with RPATH if GNU ld is used + + # darwin + sys.platform = 'darwin' + self.assertEqual(self.cc.rpath_foo(), '-L/foo') + + # hp-ux + sys.platform = 'hp-ux' + old_gcv = sysconfig.get_config_var + + def gcv(v): + return 'xxx' + sysconfig.get_config_var = gcv + self.assertEqual(self.cc.rpath_foo(), ['+s', '-L/foo']) + + def gcv(v): + return 'gcc' + sysconfig.get_config_var = gcv + self.assertEqual(self.cc.rpath_foo(), ['-Wl,+s', '-L/foo']) + + def gcv(v): + return 'g++' + sysconfig.get_config_var = gcv + self.assertEqual(self.cc.rpath_foo(), ['-Wl,+s', '-L/foo']) + + sysconfig.get_config_var = old_gcv + + # irix646 + sys.platform = 'irix646' + self.assertEqual(self.cc.rpath_foo(), ['-rpath', '/foo']) + + # osf1V5 + sys.platform = 'osf1V5' + self.assertEqual(self.cc.rpath_foo(), ['-rpath', '/foo']) + + # GCC GNULD + sys.platform = 'bar' + + def gcv(v): + if v == 'CC': + return 'gcc' + elif v == 'GNULD': + return 'yes' + sysconfig.get_config_var = gcv + self.assertEqual(self.cc.rpath_foo(), '-Wl,--enable-new-dtags,-R/foo') + + # GCC non-GNULD + sys.platform = 'bar' + + def gcv(v): + if v == 'CC': + return 'gcc' + elif v == 'GNULD': + return 'no' + sysconfig.get_config_var = gcv + self.assertEqual(self.cc.rpath_foo(), '-Wl,-R/foo') + + # GCC GNULD with fully qualified configuration prefix + # see #7617 + sys.platform = 'bar' + + def gcv(v): + if v == 'CC': + return 'x86_64-pc-linux-gnu-gcc-4.4.2' + elif v == 'GNULD': + return 'yes' + sysconfig.get_config_var = gcv + self.assertEqual(self.cc.rpath_foo(), '-Wl,--enable-new-dtags,-R/foo') + + # non-GCC GNULD + sys.platform = 'bar' + + def gcv(v): + if v == 'CC': + return 'cc' + elif v == 'GNULD': + return 'yes' + sysconfig.get_config_var = gcv + self.assertEqual(self.cc.rpath_foo(), '-R/foo') + + # non-GCC non-GNULD + sys.platform = 'bar' + + def gcv(v): + if v == 'CC': + return 'cc' + elif v == 'GNULD': + return 'no' + sysconfig.get_config_var = gcv + self.assertEqual(self.cc.rpath_foo(), '-R/foo') + + # AIX C/C++ linker + sys.platform = 'aix' + + def gcv(v): + return 'xxx' + sysconfig.get_config_var = gcv + self.assertEqual(self.cc.rpath_foo(), '-blibpath:/foo') + + +def test_suite(): + return unittest.makeSuite(UnixCCompilerTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_util.py b/Lib/packaging/tests/test_util.py new file mode 100644 index 0000000..336086d --- /dev/null +++ b/Lib/packaging/tests/test_util.py @@ -0,0 +1,928 @@ +"""Tests for packaging.util.""" +import os +import sys +import time +import logging +import tempfile +import subprocess +from io import StringIO + +from packaging.tests import support, unittest +from packaging.errors import ( + PackagingPlatformError, PackagingByteCompileError, PackagingFileError, + PackagingExecError, InstallationException) +from packaging import util +from packaging.util import ( + convert_path, change_root, split_quoted, strtobool, rfc822_escape, + get_compiler_versions, _MAC_OS_X_LD_VERSION, byte_compile, find_packages, + spawn, get_pypirc_path, generate_pypirc, read_pypirc, resolve_name, iglob, + RICH_GLOB, egginfo_to_distinfo, is_setuptools, is_distutils, is_packaging, + get_install_method) + + +PYPIRC = """\ +[distutils] +index-servers = + pypi + server1 + +[pypi] +username:me +password:xxxx + +[server1] +repository:http://example.com +username:tarek +password:secret +""" + +PYPIRC_OLD = """\ +[server-login] +username:tarek +password:secret +""" + +WANTED = """\ +[distutils] +index-servers = + pypi + +[pypi] +username:tarek +password:xxx +""" + + +class FakePopen: + test_class = None + + def __init__(self, cmd, shell, stdout, stderr): + self.cmd = cmd.split()[0] + exes = self.test_class._exes + if self.cmd not in exes: + # we don't want to call the system, returning an empty + # output so it doesn't match + self.stdout = StringIO() + self.stderr = StringIO() + else: + self.stdout = StringIO(exes[self.cmd]) + self.stderr = StringIO() + + +class UtilTestCase(support.EnvironRestorer, + support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + restore_environ = ['HOME'] + + def setUp(self): + super(UtilTestCase, self).setUp() + self.tmp_dir = self.mkdtemp() + self.rc = os.path.join(self.tmp_dir, '.pypirc') + os.environ['HOME'] = self.tmp_dir + # saving the environment + self.name = os.name + self.platform = sys.platform + self.version = sys.version + self.sep = os.sep + self.join = os.path.join + self.isabs = os.path.isabs + self.splitdrive = os.path.splitdrive + #self._config_vars = copy(sysconfig._config_vars) + + # patching os.uname + if hasattr(os, 'uname'): + self.uname = os.uname + self._uname = os.uname() + else: + self.uname = None + self._uname = None + os.uname = self._get_uname + + # patching POpen + self.old_find_executable = util.find_executable + util.find_executable = self._find_executable + self._exes = {} + self.old_popen = subprocess.Popen + self.old_stdout = sys.stdout + self.old_stderr = sys.stderr + FakePopen.test_class = self + subprocess.Popen = FakePopen + + def tearDown(self): + # getting back the environment + os.name = self.name + sys.platform = self.platform + sys.version = self.version + os.sep = self.sep + os.path.join = self.join + os.path.isabs = self.isabs + os.path.splitdrive = self.splitdrive + if self.uname is not None: + os.uname = self.uname + else: + del os.uname + #sysconfig._config_vars = copy(self._config_vars) + util.find_executable = self.old_find_executable + subprocess.Popen = self.old_popen + sys.old_stdout = self.old_stdout + sys.old_stderr = self.old_stderr + super(UtilTestCase, self).tearDown() + + def _set_uname(self, uname): + self._uname = uname + + def _get_uname(self): + return self._uname + + def test_convert_path(self): + # linux/mac + os.sep = '/' + + def _join(path): + return '/'.join(path) + os.path.join = _join + + self.assertEqual(convert_path('/home/to/my/stuff'), + '/home/to/my/stuff') + + # win + os.sep = '\\' + + def _join(*path): + return '\\'.join(path) + os.path.join = _join + + self.assertRaises(ValueError, convert_path, '/home/to/my/stuff') + self.assertRaises(ValueError, convert_path, 'home/to/my/stuff/') + + self.assertEqual(convert_path('home/to/my/stuff'), + 'home\\to\\my\\stuff') + self.assertEqual(convert_path('.'), + os.curdir) + + def test_change_root(self): + # linux/mac + os.name = 'posix' + + def _isabs(path): + return path[0] == '/' + os.path.isabs = _isabs + + def _join(*path): + return '/'.join(path) + os.path.join = _join + + self.assertEqual(change_root('/root', '/old/its/here'), + '/root/old/its/here') + self.assertEqual(change_root('/root', 'its/here'), + '/root/its/here') + + # windows + os.name = 'nt' + + def _isabs(path): + return path.startswith('c:\\') + os.path.isabs = _isabs + + def _splitdrive(path): + if path.startswith('c:'): + return '', path.replace('c:', '') + return '', path + os.path.splitdrive = _splitdrive + + def _join(*path): + return '\\'.join(path) + os.path.join = _join + + self.assertEqual(change_root('c:\\root', 'c:\\old\\its\\here'), + 'c:\\root\\old\\its\\here') + self.assertEqual(change_root('c:\\root', 'its\\here'), + 'c:\\root\\its\\here') + + # BugsBunny os (it's a great os) + os.name = 'BugsBunny' + self.assertRaises(PackagingPlatformError, + change_root, 'c:\\root', 'its\\here') + + # XXX platforms to be covered: os2, mac + + def test_split_quoted(self): + self.assertEqual(split_quoted('""one"" "two" \'three\' \\four'), + ['one', 'two', 'three', 'four']) + + def test_strtobool(self): + yes = ('y', 'Y', 'yes', 'True', 't', 'true', 'True', 'On', 'on', '1') + no = ('n', 'no', 'f', 'false', 'off', '0', 'Off', 'No', 'N') + + for y in yes: + self.assertTrue(strtobool(y)) + + for n in no: + self.assertFalse(strtobool(n)) + + def test_rfc822_escape(self): + header = 'I am a\npoor\nlonesome\nheader\n' + res = rfc822_escape(header) + wanted = ('I am a%(8s)spoor%(8s)slonesome%(8s)s' + 'header%(8s)s') % {'8s': '\n' + 8 * ' '} + self.assertEqual(res, wanted) + + def test_find_exe_version(self): + # the ld version scheme under MAC OS is: + # ^@(#)PROGRAM:ld PROJECT:ld64-VERSION + # + # where VERSION is a 2-digit number for major + # revisions. For instance under Leopard, it's + # currently 77 + # + # Dots are used when branching is done. + # + # The SnowLeopard ld64 is currently 95.2.12 + + for output, version in (('@(#)PROGRAM:ld PROJECT:ld64-77', '77'), + ('@(#)PROGRAM:ld PROJECT:ld64-95.2.12', + '95.2.12')): + result = _MAC_OS_X_LD_VERSION.search(output) + self.assertEqual(result.group(1), version) + + def _find_executable(self, name): + if name in self._exes: + return name + return None + + def test_get_compiler_versions(self): + # get_versions calls distutils.spawn.find_executable on + # 'gcc', 'ld' and 'dllwrap' + self.assertEqual(get_compiler_versions(), (None, None, None)) + + # Let's fake we have 'gcc' and it returns '3.4.5' + self._exes['gcc'] = 'gcc (GCC) 3.4.5 (mingw special)\nFSF' + res = get_compiler_versions() + self.assertEqual(str(res[0]), '3.4.5') + + # and let's see what happens when the version + # doesn't match the regular expression + # (\d+\.\d+(\.\d+)*) + self._exes['gcc'] = 'very strange output' + res = get_compiler_versions() + self.assertEqual(res[0], None) + + # same thing for ld + if sys.platform != 'darwin': + self._exes['ld'] = 'GNU ld version 2.17.50 20060824' + res = get_compiler_versions() + self.assertEqual(str(res[1]), '2.17.50') + self._exes['ld'] = '@(#)PROGRAM:ld PROJECT:ld64-77' + res = get_compiler_versions() + self.assertEqual(res[1], None) + else: + self._exes['ld'] = 'GNU ld version 2.17.50 20060824' + res = get_compiler_versions() + self.assertEqual(res[1], None) + self._exes['ld'] = '@(#)PROGRAM:ld PROJECT:ld64-77' + res = get_compiler_versions() + self.assertEqual(str(res[1]), '77') + + # and dllwrap + self._exes['dllwrap'] = 'GNU dllwrap 2.17.50 20060824\nFSF' + res = get_compiler_versions() + self.assertEqual(str(res[2]), '2.17.50') + self._exes['dllwrap'] = 'Cheese Wrap' + res = get_compiler_versions() + self.assertEqual(res[2], None) + + @unittest.skipUnless(hasattr(sys, 'dont_write_bytecode'), + 'sys.dont_write_bytecode not supported') + def test_dont_write_bytecode(self): + # makes sure byte_compile raise a PackagingError + # if sys.dont_write_bytecode is True + old_dont_write_bytecode = sys.dont_write_bytecode + sys.dont_write_bytecode = True + try: + self.assertRaises(PackagingByteCompileError, byte_compile, []) + finally: + sys.dont_write_bytecode = old_dont_write_bytecode + + def test_newer(self): + self.assertRaises(PackagingFileError, util.newer, 'xxx', 'xxx') + self.newer_f1 = self.mktempfile() + time.sleep(1) + self.newer_f2 = self.mktempfile() + self.assertTrue(util.newer(self.newer_f2.name, self.newer_f1.name)) + + def test_find_packages(self): + # let's create a structure we want to scan: + # + # pkg1 + # __init__ + # pkg2 + # __init__ + # pkg3 + # __init__ + # pkg6 + # __init__ + # pkg4 <--- not a pkg + # pkg8 + # __init__ + # pkg5 + # __init__ + # + root = self.mkdtemp() + pkg1 = os.path.join(root, 'pkg1') + os.mkdir(pkg1) + self.write_file(os.path.join(pkg1, '__init__.py')) + os.mkdir(os.path.join(pkg1, 'pkg2')) + self.write_file(os.path.join(pkg1, 'pkg2', '__init__.py')) + os.mkdir(os.path.join(pkg1, 'pkg3')) + self.write_file(os.path.join(pkg1, 'pkg3', '__init__.py')) + os.mkdir(os.path.join(pkg1, 'pkg3', 'pkg6')) + self.write_file(os.path.join(pkg1, 'pkg3', 'pkg6', '__init__.py')) + os.mkdir(os.path.join(pkg1, 'pkg4')) + os.mkdir(os.path.join(pkg1, 'pkg4', 'pkg8')) + self.write_file(os.path.join(pkg1, 'pkg4', 'pkg8', '__init__.py')) + pkg5 = os.path.join(root, 'pkg5') + os.mkdir(pkg5) + self.write_file(os.path.join(pkg5, '__init__.py')) + + res = find_packages([root], ['pkg1.pkg2']) + self.assertEqual(set(res), set(['pkg1', 'pkg5', 'pkg1.pkg3', + 'pkg1.pkg3.pkg6'])) + + def test_resolve_name(self): + self.assertIs(str, resolve_name('builtins.str')) + self.assertEqual( + UtilTestCase.__name__, + resolve_name("packaging.tests.test_util.UtilTestCase").__name__) + self.assertEqual( + UtilTestCase.test_resolve_name.__name__, + resolve_name("packaging.tests.test_util.UtilTestCase." + "test_resolve_name").__name__) + + self.assertRaises(ImportError, resolve_name, + "packaging.tests.test_util.UtilTestCaseNot") + self.assertRaises(ImportError, resolve_name, + "packaging.tests.test_util.UtilTestCase." + "nonexistent_attribute") + + def test_import_nested_first_time(self): + tmp_dir = self.mkdtemp() + os.makedirs(os.path.join(tmp_dir, 'a', 'b')) + self.write_file(os.path.join(tmp_dir, 'a', '__init__.py'), '') + self.write_file(os.path.join(tmp_dir, 'a', 'b', '__init__.py'), '') + self.write_file(os.path.join(tmp_dir, 'a', 'b', 'c.py'), + 'class Foo: pass') + + try: + sys.path.append(tmp_dir) + resolve_name("a.b.c.Foo") + # assert nothing raised + finally: + sys.path.remove(tmp_dir) + + @unittest.skipIf(sys.version < '2.6', 'requires Python 2.6 or higher') + def test_run_2to3_on_code(self): + content = "print 'test'" + converted_content = "print('test')" + file_handle = self.mktempfile() + file_name = file_handle.name + file_handle.write(content) + file_handle.flush() + file_handle.seek(0) + from packaging.util import run_2to3 + run_2to3([file_name]) + new_content = "".join(file_handle.read()) + file_handle.close() + self.assertEqual(new_content, converted_content) + + @unittest.skipIf(sys.version < '2.6', 'requires Python 2.6 or higher') + def test_run_2to3_on_doctests(self): + # to check if text files containing doctests only get converted. + content = ">>> print 'test'\ntest\n" + converted_content = ">>> print('test')\ntest\n\n" + file_handle = self.mktempfile() + file_name = file_handle.name + file_handle.write(content) + file_handle.flush() + file_handle.seek(0) + from packaging.util import run_2to3 + run_2to3([file_name], doctests_only=True) + new_content = "".join(file_handle.readlines()) + file_handle.close() + self.assertEqual(new_content, converted_content) + + @unittest.skipUnless(os.name in ('nt', 'posix'), + 'runs only under posix or nt') + def test_spawn(self): + # Do not patch subprocess on unix because + # packaging.util._spawn_posix uses it + if os.name in 'posix': + subprocess.Popen = self.old_popen + tmpdir = self.mkdtemp() + + # creating something executable + # through the shell that returns 1 + if os.name == 'posix': + exe = os.path.join(tmpdir, 'foo.sh') + self.write_file(exe, '#!/bin/sh\nexit 1') + os.chmod(exe, 0o777) + else: + exe = os.path.join(tmpdir, 'foo.bat') + self.write_file(exe, 'exit 1') + + os.chmod(exe, 0o777) + self.assertRaises(PackagingExecError, spawn, [exe]) + + # now something that works + if os.name == 'posix': + exe = os.path.join(tmpdir, 'foo.sh') + self.write_file(exe, '#!/bin/sh\nexit 0') + os.chmod(exe, 0o777) + else: + exe = os.path.join(tmpdir, 'foo.bat') + self.write_file(exe, 'exit 0') + + os.chmod(exe, 0o777) + spawn([exe]) # should work without any error + + def test_server_registration(self): + # This test makes sure we know how to: + # 1. handle several sections in .pypirc + # 2. handle the old format + + # new format + self.write_file(self.rc, PYPIRC) + config = read_pypirc() + + config = sorted(config.items()) + expected = [('password', 'xxxx'), ('realm', 'pypi'), + ('repository', 'http://pypi.python.org/pypi'), + ('server', 'pypi'), ('username', 'me')] + self.assertEqual(config, expected) + + # old format + self.write_file(self.rc, PYPIRC_OLD) + config = read_pypirc() + config = sorted(config.items()) + expected = [('password', 'secret'), ('realm', 'pypi'), + ('repository', 'http://pypi.python.org/pypi'), + ('server', 'server-login'), ('username', 'tarek')] + self.assertEqual(config, expected) + + def test_server_empty_registration(self): + rc = get_pypirc_path() + self.assertFalse(os.path.exists(rc)) + generate_pypirc('tarek', 'xxx') + self.assertTrue(os.path.exists(rc)) + with open(rc) as f: + content = f.read() + self.assertEqual(content, WANTED) + + +class GlobTestCaseBase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + def build_files_tree(self, files): + tempdir = self.mkdtemp() + for filepath in files: + is_dir = filepath.endswith('/') + filepath = os.path.join(tempdir, *filepath.split('/')) + if is_dir: + dirname = filepath + else: + dirname = os.path.dirname(filepath) + if dirname and not os.path.exists(dirname): + os.makedirs(dirname) + if not is_dir: + self.write_file(filepath, 'babar') + return tempdir + + @staticmethod + def os_dependent_path(path): + path = path.rstrip('/').split('/') + return os.path.join(*path) + + def clean_tree(self, spec): + files = [] + for path, includes in spec.items(): + if includes: + files.append(self.os_dependent_path(path)) + return files + + +class GlobTestCase(GlobTestCaseBase): + + def assertGlobMatch(self, glob, spec): + """""" + tempdir = self.build_files_tree(spec) + expected = self.clean_tree(spec) + self.addCleanup(os.chdir, os.getcwd()) + os.chdir(tempdir) + result = list(iglob(glob)) + self.assertCountEqual(expected, result) + + def test_regex_rich_glob(self): + matches = RICH_GLOB.findall( + r"babar aime les {fraises} est les {huitres}") + self.assertEqual(["fraises", "huitres"], matches) + + def test_simple_glob(self): + glob = '*.tp?' + spec = {'coucou.tpl': True, + 'coucou.tpj': True, + 'Donotwant': False} + self.assertGlobMatch(glob, spec) + + def test_simple_glob_in_dir(self): + glob = 'babar/*.tp?' + spec = {'babar/coucou.tpl': True, + 'babar/coucou.tpj': True, + 'babar/toto.bin': False, + 'Donotwant': False} + self.assertGlobMatch(glob, spec) + + def test_recursive_glob_head(self): + glob = '**/tip/*.t?l' + spec = {'babar/zaza/zuzu/tip/coucou.tpl': True, + 'babar/z/tip/coucou.tpl': True, + 'babar/tip/coucou.tpl': True, + 'babar/zeop/tip/babar/babar.tpl': False, + 'babar/z/tip/coucou.bin': False, + 'babar/toto.bin': False, + 'zozo/zuzu/tip/babar.tpl': True, + 'zozo/tip/babar.tpl': True, + 'Donotwant': False} + self.assertGlobMatch(glob, spec) + + def test_recursive_glob_tail(self): + glob = 'babar/**' + spec = {'babar/zaza/': True, + 'babar/zaza/zuzu/': True, + 'babar/zaza/zuzu/babar.xml': True, + 'babar/zaza/zuzu/toto.xml': True, + 'babar/zaza/zuzu/toto.csv': True, + 'babar/zaza/coucou.tpl': True, + 'babar/bubu.tpl': True, + 'zozo/zuzu/tip/babar.tpl': False, + 'zozo/tip/babar.tpl': False, + 'Donotwant': False} + self.assertGlobMatch(glob, spec) + + def test_recursive_glob_middle(self): + glob = 'babar/**/tip/*.t?l' + spec = {'babar/zaza/zuzu/tip/coucou.tpl': True, + 'babar/z/tip/coucou.tpl': True, + 'babar/tip/coucou.tpl': True, + 'babar/zeop/tip/babar/babar.tpl': False, + 'babar/z/tip/coucou.bin': False, + 'babar/toto.bin': False, + 'zozo/zuzu/tip/babar.tpl': False, + 'zozo/tip/babar.tpl': False, + 'Donotwant': False} + self.assertGlobMatch(glob, spec) + + def test_glob_set_tail(self): + glob = 'bin/*.{bin,sh,exe}' + spec = {'bin/babar.bin': True, + 'bin/zephir.sh': True, + 'bin/celestine.exe': True, + 'bin/cornelius.bat': False, + 'bin/cornelius.xml': False, + 'toto/yurg': False, + 'Donotwant': False} + self.assertGlobMatch(glob, spec) + + def test_glob_set_middle(self): + glob = 'xml/{babar,toto}.xml' + spec = {'xml/babar.xml': True, + 'xml/toto.xml': True, + 'xml/babar.xslt': False, + 'xml/cornelius.sgml': False, + 'xml/zephir.xml': False, + 'toto/yurg.xml': False, + 'Donotwant': False} + self.assertGlobMatch(glob, spec) + + def test_glob_set_head(self): + glob = '{xml,xslt}/babar.*' + spec = {'xml/babar.xml': True, + 'xml/toto.xml': False, + 'xslt/babar.xslt': True, + 'xslt/toto.xslt': False, + 'toto/yurg.xml': False, + 'Donotwant': False} + self.assertGlobMatch(glob, spec) + + def test_glob_all(self): + glob = '{xml/*,xslt/**}/babar.xml' + spec = {'xml/a/babar.xml': True, + 'xml/b/babar.xml': True, + 'xml/a/c/babar.xml': False, + 'xslt/a/babar.xml': True, + 'xslt/b/babar.xml': True, + 'xslt/a/c/babar.xml': True, + 'toto/yurg.xml': False, + 'Donotwant': False} + self.assertGlobMatch(glob, spec) + + def test_invalid_glob_pattern(self): + invalids = [ + 'ppooa**', + 'azzaeaz4**/', + '/**ddsfs', + '**##1e"&e', + 'DSFb**c009', + '{', + '{aaQSDFa', + '}', + 'aQSDFSaa}', + '{**a,', + ',**a}', + '{a**,', + ',b**}', + '{a**a,babar}', + '{bob,b**z}', + ] + msg = "%r is not supposed to be a valid pattern" + for pattern in invalids: + try: + iglob(pattern) + except ValueError: + continue + else: + self.fail(msg % pattern) + + +class EggInfoToDistInfoTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + def get_metadata_file_paths(self, distinfo_path): + req_metadata_files = ['METADATA', 'RECORD', 'INSTALLER'] + metadata_file_paths = [] + for metadata_file in req_metadata_files: + path = os.path.join(distinfo_path, metadata_file) + metadata_file_paths.append(path) + return metadata_file_paths + + def test_egginfo_to_distinfo_setuptools(self): + distinfo = 'hello-0.1.1-py3.3.dist-info' + egginfo = 'hello-0.1.1-py3.3.egg-info' + dirs = [egginfo] + files = ['hello.py', 'hello.pyc'] + extra_metadata = ['dependency_links.txt', 'entry_points.txt', + 'not-zip-safe', 'PKG-INFO', 'top_level.txt', + 'SOURCES.txt'] + for f in extra_metadata: + files.append(os.path.join(egginfo, f)) + + tempdir, record_file = self.build_dist_tree(files, dirs) + distinfo_path = os.path.join(tempdir, distinfo) + egginfo_path = os.path.join(tempdir, egginfo) + metadata_file_paths = self.get_metadata_file_paths(distinfo_path) + + egginfo_to_distinfo(record_file) + # test that directories and files get created + self.assertTrue(os.path.isdir(distinfo_path)) + self.assertTrue(os.path.isdir(egginfo_path)) + + for mfile in metadata_file_paths: + self.assertTrue(os.path.isfile(mfile)) + + def test_egginfo_to_distinfo_distutils(self): + distinfo = 'hello-0.1.1-py3.3.dist-info' + egginfo = 'hello-0.1.1-py3.3.egg-info' + # egginfo is a file in distutils which contains the metadata + files = ['hello.py', 'hello.pyc', egginfo] + + tempdir, record_file = self.build_dist_tree(files, dirs=[]) + distinfo_path = os.path.join(tempdir, distinfo) + egginfo_path = os.path.join(tempdir, egginfo) + metadata_file_paths = self.get_metadata_file_paths(distinfo_path) + + egginfo_to_distinfo(record_file) + # test that directories and files get created + self.assertTrue(os.path.isdir(distinfo_path)) + self.assertTrue(os.path.isfile(egginfo_path)) + + for mfile in metadata_file_paths: + self.assertTrue(os.path.isfile(mfile)) + + def build_dist_tree(self, files, dirs): + tempdir = self.mkdtemp() + record_file_path = os.path.join(tempdir, 'RECORD') + file_paths, dir_paths = ([], []) + for d in dirs: + path = os.path.join(tempdir, d) + os.makedirs(path) + dir_paths.append(path) + for f in files: + path = os.path.join(tempdir, f) + _f = open(path, 'w') + _f.write(f) + _f.close() + file_paths.append(path) + + record_file = open(record_file_path, 'w') + for fpath in file_paths: + record_file.write(fpath + '\n') + for dpath in dir_paths: + record_file.write(dpath + '\n') + record_file.close() + + return (tempdir, record_file_path) + + +class PackagingLibChecks(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + def setUp(self): + super(PackagingLibChecks, self).setUp() + self._empty_dir = self.mkdtemp() + + def test_empty_package_is_not_based_on_anything(self): + self.assertFalse(is_setuptools(self._empty_dir)) + self.assertFalse(is_distutils(self._empty_dir)) + self.assertFalse(is_packaging(self._empty_dir)) + + def test_setup_py_importing_setuptools_is_setuptools_based(self): + self.assertTrue(is_setuptools(self._setuptools_setup_py_pkg())) + + def test_egg_info_dir_and_setup_py_is_setuptools_based(self): + self.assertTrue(is_setuptools(self._setuptools_egg_info_pkg())) + + def test_egg_info_and_non_setuptools_setup_py_is_setuptools_based(self): + self.assertTrue(is_setuptools(self._egg_info_with_no_setuptools())) + + def test_setup_py_not_importing_setuptools_is_not_setuptools_based(self): + self.assertFalse(is_setuptools(self._random_setup_py_pkg())) + + def test_setup_py_importing_distutils_is_distutils_based(self): + self.assertTrue(is_distutils(self._distutils_setup_py_pkg())) + + def test_pkg_info_file_and_setup_py_is_distutils_based(self): + self.assertTrue(is_distutils(self._distutils_pkg_info())) + + def test_pkg_info_and_non_distutils_setup_py_is_distutils_based(self): + self.assertTrue(is_distutils(self._pkg_info_with_no_distutils())) + + def test_setup_py_not_importing_distutils_is_not_distutils_based(self): + self.assertFalse(is_distutils(self._random_setup_py_pkg())) + + def test_setup_cfg_with_no_metadata_section_is_not_packaging_based(self): + self.assertFalse(is_packaging(self._setup_cfg_with_no_metadata_pkg())) + + def test_setup_cfg_with_valid_metadata_section_is_packaging_based(self): + self.assertTrue(is_packaging(self._valid_setup_cfg_pkg())) + + def test_setup_cfg_and_invalid_setup_cfg_is_not_packaging_based(self): + self.assertFalse(is_packaging(self._invalid_setup_cfg_pkg())) + + def test_get_install_method_with_setuptools_pkg(self): + path = self._setuptools_setup_py_pkg() + self.assertEqual("setuptools", get_install_method(path)) + + def test_get_install_method_with_distutils_pkg(self): + path = self._distutils_pkg_info() + self.assertEqual("distutils", get_install_method(path)) + + def test_get_install_method_with_packaging_pkg(self): + path = self._valid_setup_cfg_pkg() + self.assertEqual("packaging", get_install_method(path)) + + def test_get_install_method_with_unknown_pkg(self): + path = self._invalid_setup_cfg_pkg() + self.assertRaises(InstallationException, get_install_method, path) + + def test_is_setuptools_logs_setup_py_text_found(self): + is_setuptools(self._setuptools_setup_py_pkg()) + expected = ['setup.py file found', 'found setuptools text in setup.py'] + self.assertEqual(expected, self.get_logs(logging.INFO)) + + def test_is_setuptools_logs_setup_py_text_not_found(self): + directory = self._random_setup_py_pkg() + is_setuptools(directory) + info_expected = ['setup.py file found'] + warn_expected = ['no egg-info directory found', + 'no setuptools text found in setup.py'] + self.assertEqual(info_expected, self.get_logs(logging.INFO)) + self.assertEqual(warn_expected, self.get_logs(logging.WARN)) + + def test_is_setuptools_logs_egg_info_dir_found(self): + is_setuptools(self._setuptools_egg_info_pkg()) + expected = ['setup.py file found', 'found egg-info directory'] + self.assertEqual(expected, self.get_logs(logging.INFO)) + + def test_is_distutils_logs_setup_py_text_found(self): + is_distutils(self._distutils_setup_py_pkg()) + expected = ['setup.py file found', 'found distutils text in setup.py'] + self.assertEqual(expected, self.get_logs(logging.INFO)) + + def test_is_distutils_logs_setup_py_text_not_found(self): + directory = self._random_setup_py_pkg() + is_distutils(directory) + info_expected = ['setup.py file found'] + warn_expected = ['no PKG-INFO file found', + 'no distutils text found in setup.py'] + self.assertEqual(info_expected, self.get_logs(logging.INFO)) + self.assertEqual(warn_expected, self.get_logs(logging.WARN)) + + def test_is_distutils_logs_pkg_info_file_found(self): + is_distutils(self._distutils_pkg_info()) + expected = ['setup.py file found', 'PKG-INFO file found'] + self.assertEqual(expected, self.get_logs(logging.INFO)) + + def test_is_packaging_logs_setup_cfg_found(self): + is_packaging(self._valid_setup_cfg_pkg()) + expected = ['setup.cfg file found'] + self.assertEqual(expected, self.get_logs(logging.INFO)) + + def test_is_packaging_logs_setup_cfg_not_found(self): + is_packaging(self._empty_dir) + expected = ['no setup.cfg file found'] + self.assertEqual(expected, self.get_logs(logging.WARN)) + + def _write_setuptools_setup_py(self, directory): + self.write_file((directory, 'setup.py'), + "from setuptools import setup") + + def _write_distutils_setup_py(self, directory): + self.write_file([directory, 'setup.py'], + "from distutils.core import setup") + + def _write_packaging_setup_cfg(self, directory): + self.write_file([directory, 'setup.cfg'], + ("[metadata]\n" + "name = mypackage\n" + "version = 0.1.0\n")) + + def _setuptools_setup_py_pkg(self): + tmp = self.mkdtemp() + self._write_setuptools_setup_py(tmp) + return tmp + + def _distutils_setup_py_pkg(self): + tmp = self.mkdtemp() + self._write_distutils_setup_py(tmp) + return tmp + + def _valid_setup_cfg_pkg(self): + tmp = self.mkdtemp() + self._write_packaging_setup_cfg(tmp) + return tmp + + def _setuptools_egg_info_pkg(self): + tmp = self.mkdtemp() + self._write_setuptools_setup_py(tmp) + tempfile.mkdtemp(suffix='.egg-info', dir=tmp) + return tmp + + def _distutils_pkg_info(self): + tmp = self._distutils_setup_py_pkg() + self.write_file([tmp, 'PKG-INFO'], '') + return tmp + + def _setup_cfg_with_no_metadata_pkg(self): + tmp = self.mkdtemp() + self.write_file([tmp, 'setup.cfg'], + ("[othersection]\n" + "foo = bar\n")) + return tmp + + def _invalid_setup_cfg_pkg(self): + tmp = self.mkdtemp() + self.write_file([tmp, 'setup.cfg'], + ("[metadata]\n" + "name = john\n" + "last_name = doe\n")) + return tmp + + def _egg_info_with_no_setuptools(self): + tmp = self._random_setup_py_pkg() + tempfile.mkdtemp(suffix='.egg-info', dir=tmp) + return tmp + + def _pkg_info_with_no_distutils(self): + tmp = self._random_setup_py_pkg() + self.write_file([tmp, 'PKG-INFO'], '') + return tmp + + def _random_setup_py_pkg(self): + tmp = self.mkdtemp() + self.write_file((tmp, 'setup.py'), "from mypackage import setup") + return tmp + + +def test_suite(): + suite = unittest.makeSuite(UtilTestCase) + suite.addTest(unittest.makeSuite(GlobTestCase)) + suite.addTest(unittest.makeSuite(EggInfoToDistInfoTestCase)) + suite.addTest(unittest.makeSuite(PackagingLibChecks)) + return suite + + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_version.py b/Lib/packaging/tests/test_version.py new file mode 100644 index 0000000..f94c800 --- /dev/null +++ b/Lib/packaging/tests/test_version.py @@ -0,0 +1,252 @@ +"""Tests for packaging.version.""" +import doctest +import os + +from packaging.version import NormalizedVersion as V +from packaging.version import HugeMajorVersionNumError, IrrationalVersionError +from packaging.version import suggest_normalized_version as suggest +from packaging.version import VersionPredicate +from packaging.tests import unittest + + +class VersionTestCase(unittest.TestCase): + + versions = ((V('1.0'), '1.0'), + (V('1.1'), '1.1'), + (V('1.2.3'), '1.2.3'), + (V('1.2'), '1.2'), + (V('1.2.3a4'), '1.2.3a4'), + (V('1.2c4'), '1.2c4'), + (V('1.2.3.4'), '1.2.3.4'), + (V('1.2.3.4.0b3'), '1.2.3.4b3'), + (V('1.2.0.0.0'), '1.2'), + (V('1.0.dev345'), '1.0.dev345'), + (V('1.0.post456.dev623'), '1.0.post456.dev623')) + + def test_repr(self): + + self.assertEqual(repr(V('1.0')), "NormalizedVersion('1.0')") + + def test_basic_versions(self): + + for v, s in self.versions: + self.assertEqual(str(v), s) + + def test_hash(self): + + for v, s in self.versions: + self.assertEqual(hash(v), hash(V(s))) + + versions = set([v for v, s in self.versions]) + for v, s in self.versions: + self.assertIn(v, versions) + + self.assertEqual(set([V('1.0')]), set([V('1.0'), V('1.0')])) + + def test_from_parts(self): + + for v, s in self.versions: + parts = v.parts + v2 = V.from_parts(*v.parts) + self.assertEqual(v, v2) + self.assertEqual(str(v), str(v2)) + + def test_irrational_versions(self): + + irrational = ('1', '1.2a', '1.2.3b', '1.02', '1.2a03', + '1.2a3.04', '1.2.dev.2', '1.2dev', '1.2.dev', + '1.2.dev2.post2', '1.2.post2.dev3.post4') + + for s in irrational: + self.assertRaises(IrrationalVersionError, V, s) + + def test_huge_version(self): + + self.assertEqual(str(V('1980.0')), '1980.0') + self.assertRaises(HugeMajorVersionNumError, V, '1981.0') + self.assertEqual(str(V('1981.0', error_on_huge_major_num=False)), + '1981.0') + + def test_comparison(self): + comparison_doctest_string = r""" + >>> V('1.2.0') == '1.2' + Traceback (most recent call last): + ... + TypeError: cannot compare NormalizedVersion and str + + >>> V('1.2') < '1.3' + Traceback (most recent call last): + ... + TypeError: cannot compare NormalizedVersion and str + + >>> V('1.2.0') == V('1.2') + True + >>> V('1.2.0') == V('1.2.3') + False + >>> V('1.2.0') != V('1.2.3') + True + >>> V('1.2.0') < V('1.2.3') + True + >>> V('1.2.0') < V('1.2.0') + False + >>> V('1.2.0') <= V('1.2.0') + True + >>> V('1.2.0') <= V('1.2.3') + True + >>> V('1.2.3') <= V('1.2.0') + False + >>> V('1.2.0') >= V('1.2.0') + True + >>> V('1.2.3') >= V('1.2.0') + True + >>> V('1.2.0') >= V('1.2.3') + False + >>> (V('1.0') > V('1.0b2')) + True + >>> (V('1.0') > V('1.0c2') > V('1.0c1') > V('1.0b2') > V('1.0b1') + ... > V('1.0a2') > V('1.0a1')) + True + >>> (V('1.0.0') > V('1.0.0c2') > V('1.0.0c1') > V('1.0.0b2') > V('1.0.0b1') + ... > V('1.0.0a2') > V('1.0.0a1')) + True + + >>> V('1.0') < V('1.0.post456.dev623') + True + + >>> V('1.0.post456.dev623') < V('1.0.post456') < V('1.0.post1234') + True + + >>> (V('1.0a1') + ... < V('1.0a2.dev456') + ... < V('1.0a2') + ... < V('1.0a2.1.dev456') # e.g. need to do a quick post release on 1.0a2 + ... < V('1.0a2.1') + ... < V('1.0b1.dev456') + ... < V('1.0b2') + ... < V('1.0c1.dev456') + ... < V('1.0c1') + ... < V('1.0.dev7') + ... < V('1.0.dev18') + ... < V('1.0.dev456') + ... < V('1.0.dev1234') + ... < V('1.0') + ... < V('1.0.post456.dev623') # development version of a post release + ... < V('1.0.post456')) + True + """ + doctest.script_from_examples(comparison_doctest_string) + + def test_suggest_normalized_version(self): + + self.assertEqual(suggest('1.0'), '1.0') + self.assertEqual(suggest('1.0-alpha1'), '1.0a1') + self.assertEqual(suggest('1.0c2'), '1.0c2') + self.assertEqual(suggest('walla walla washington'), None) + self.assertEqual(suggest('2.4c1'), '2.4c1') + self.assertEqual(suggest('v1.0'), '1.0') + + # from setuptools + self.assertEqual(suggest('0.4a1.r10'), '0.4a1.post10') + self.assertEqual(suggest('0.7a1dev-r66608'), '0.7a1.dev66608') + self.assertEqual(suggest('0.6a9.dev-r41475'), '0.6a9.dev41475') + self.assertEqual(suggest('2.4preview1'), '2.4c1') + self.assertEqual(suggest('2.4pre1'), '2.4c1') + self.assertEqual(suggest('2.1-rc2'), '2.1c2') + + # from pypi + self.assertEqual(suggest('0.1dev'), '0.1.dev0') + self.assertEqual(suggest('0.1.dev'), '0.1.dev0') + + # we want to be able to parse Twisted + # development versions are like post releases in Twisted + self.assertEqual(suggest('9.0.0+r2363'), '9.0.0.post2363') + + # pre-releases are using markers like "pre1" + self.assertEqual(suggest('9.0.0pre1'), '9.0.0c1') + + # we want to be able to parse Tcl-TK + # they us "p1" "p2" for post releases + self.assertEqual(suggest('1.4p1'), '1.4.post1') + + def test_predicate(self): + # VersionPredicate knows how to parse stuff like: + # + # Project (>=version, ver2) + + predicates = ('zope.interface (>3.5.0)', + 'AnotherProject (3.4)', + 'OtherProject (<3.0)', + 'NoVersion', + 'Hey (>=2.5,<2.7)') + + for predicate in predicates: + v = VersionPredicate(predicate) + + self.assertTrue(VersionPredicate('Hey (>=2.5,<2.7)').match('2.6')) + self.assertTrue(VersionPredicate('Ho').match('2.6')) + self.assertFalse(VersionPredicate('Hey (>=2.5,!=2.6,<2.7)').match('2.6')) + self.assertTrue(VersionPredicate('Ho (<3.0)').match('2.6')) + self.assertTrue(VersionPredicate('Ho (<3.0,!=2.5)').match('2.6.0')) + self.assertFalse(VersionPredicate('Ho (<3.0,!=2.6)').match('2.6.0')) + self.assertTrue(VersionPredicate('Ho (2.5)').match('2.5.4')) + self.assertFalse(VersionPredicate('Ho (!=2.5)').match('2.5.2')) + self.assertTrue(VersionPredicate('Hey (<=2.5)').match('2.5.9')) + self.assertFalse(VersionPredicate('Hey (<=2.5)').match('2.6.0')) + self.assertTrue(VersionPredicate('Hey (>=2.5)').match('2.5.1')) + + self.assertRaises(ValueError, VersionPredicate, '') + + self.assertTrue(VersionPredicate('Hey 2.5').match('2.5.1')) + + # XXX need to silent the micro version in this case + self.assertFalse(VersionPredicate('Ho (<3.0,!=2.6)').match('2.6.3')) + + # Make sure a predicate that ends with a number works + self.assertTrue(VersionPredicate('virtualenv5 (1.0)').match('1.0')) + self.assertTrue(VersionPredicate('virtualenv5').match('1.0')) + self.assertTrue(VersionPredicate('vi5two').match('1.0')) + self.assertTrue(VersionPredicate('5two').match('1.0')) + self.assertTrue(VersionPredicate('vi5two 1.0').match('1.0')) + self.assertTrue(VersionPredicate('5two 1.0').match('1.0')) + + # test repr + for predicate in predicates: + self.assertEqual(str(VersionPredicate(predicate)), predicate) + + def test_predicate_name(self): + # Test that names are parsed the right way + + self.assertEqual('Hey', VersionPredicate('Hey (<1.1)').name) + self.assertEqual('Foo-Bar', VersionPredicate('Foo-Bar (1.1)').name) + self.assertEqual('Foo Bar', VersionPredicate('Foo Bar (1.1)').name) + + def test_is_final(self): + # VersionPredicate knows is a distribution is a final one or not. + final_versions = ('1.0', '1.0.post456') + other_versions = ('1.0.dev1', '1.0a2', '1.0c3') + + for version in final_versions: + self.assertTrue(V(version).is_final) + for version in other_versions: + self.assertFalse(V(version).is_final) + + +class VersionWhiteBoxTestCase(unittest.TestCase): + + def test_parse_numdots(self): + # For code coverage completeness, as pad_zeros_length can't be set or + # influenced from the public interface + self.assertEqual(V('1.0')._parse_numdots('1.0', '1.0', + pad_zeros_length=3), + [1, 0, 0]) + + +def test_suite(): + #README = os.path.join(os.path.dirname(__file__), 'README.txt') + #suite = [doctest.DocFileSuite(README), unittest.makeSuite(VersionTestCase)] + suite = [unittest.makeSuite(VersionTestCase), + unittest.makeSuite(VersionWhiteBoxTestCase)] + return unittest.TestSuite(suite) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") |