Detailed tests of pypi.py
===========================

.. :doctest:
.. :teardown: zest.releaser.tests.functional.restore_mupload

Note on test setup: we don't use the "big" setup/teardown methods here.

    >>> from zest.releaser import pypi
    >>> import pkg_resources


Multiple pypi servers support detection
---------------------------------------

The module attempts to import ``mupload`` from collective.dist and sets it to
None otherwise.  We don't influence the setup on the machine where this test
runs, so we can only check if it is available:

    >>> dont_care = pypi.mupload

If mupload is not None, collective.dist is available, otherwise not.

    >>> pypi.mupload = 'something'
    >>> pypi.collective_dist_available()
    True
    >>> pypi.mupload = None
    >>> pypi.collective_dist_available()
    False

A check for python versions is also made.

    >>> import sys
    >>> orig_version = sys.version_info

Python 2.6 and higher provide the needed functionality (in distutils;
collective.dist just backports that to older python versions):

    >>> sys.version_info = (2, 5)
    >>> pypi.new_distutils_available()
    False
    >>> sys.version_info = (2, 6)
    >>> pypi.new_distutils_available()
    True
    >>> sys.version_info = (3, 1)
    >>> pypi.new_distutils_available()
    True

If any of these two is true, multiple pypi support is available.

    >>> pypi.mupload = None
    >>> sys.version_info = (2, 5)
    >>> pypi.multiple_pypi_support()
    False
    >>> pypi.mupload = 'available'
    >>> sys.version_info = (2, 5)
    >>> pypi.multiple_pypi_support()
    True
    >>> pypi.mupload = None
    >>> sys.version_info = (2, 6)
    >>> pypi.multiple_pypi_support()
    True
    >>> pypi.mupload = 'available'
    >>> sys.version_info = (2, 6)
    >>> pypi.multiple_pypi_support()
    True

For the rest of the tests we assume that collective.dist is available:

    >>> pypi.mupload = 'mock that collective.dist is available'



Parsing the configuration file
------------------------------

For pypi uploads and collective.dist-style uploads to multiple servers, a
configuration file needs to be available:

    >>> pypi.DIST_CONFIG_FILE
    '.pypirc'

This is the default.  For testing purposes, you *can* pass in a config file's
name yourself.  We'll do that in the rest of these tests.

A missing file doesn't lead to an error:

    >>> pypiconfig = pypi.PypiConfig(config_filename='non/existing')
    >>> pypiconfig.config == None
    True

There are two styles of ``.pypirc`` files.  The old one just for pypi:

    >>> pypirc_old = pkg_resources.resource_filename(
    ...     'zest.releaser.tests', 'pypirc_old.txt')
    >>> print open(pypirc_old).read()
    [server-login]
    username:pipo_de_clown
    password:asjemenou
    >>> pypiconfig = pypi.PypiConfig(config_filename=pypirc_old)
    >>> pypiconfig.config
    <ConfigParser.ConfigParser instance at ...>
    >>> pypiconfig.is_old_pypi_config()
    True
    >>> pypiconfig.is_new_pypi_config()
    False

And the new format that allows multiple uploads:

    >>> pypirc_new = pkg_resources.resource_filename(
    ...     'zest.releaser.tests', 'pypirc_new.txt')
    >>> print open(pypirc_new).read()
    [distutils]
    index-servers =
        pypi
        plone.org
        mycompany
    <BLANKLINE>
    [pypi]
    username:user
    password:password
    <BLANKLINE>
    [plone.org]
    repository:http://plone.org/products
    username:ploneuser
    password:password
    <BLANKLINE>
    [plone.org]
    repository:http://my.company/products
    username:user
    password:password
    >>> pypiconfig = pypi.PypiConfig(config_filename=pypirc_new)
    >>> pypiconfig.config
    <ConfigParser.ConfigParser instance at ...>
    >>> pypiconfig.is_old_pypi_config()
    False
    >>> pypiconfig.is_new_pypi_config()
    True

A file with both is also possible:

    >>> pypirc_both = pkg_resources.resource_filename(
    ...     'zest.releaser.tests', 'pypirc_both.txt')
    >>> print open(pypirc_both).read()
    [server-login]
    username:bdfl
    password:secret
    <BLANKLINE>
    [distutils]
    index-servers =
      pypi
      local
    <BLANKLINE>
    [pypi]
    username:bdfl
    password:secret
    <BLANKLINE>
    [local]
    repository:http://localhost:8080/test/products
    username:bdfl
    password:secret
    >>> pypiconfig = pypi.PypiConfig(config_filename=pypirc_both)
    >>> pypiconfig.config
    <ConfigParser.ConfigParser instance at ...>
    >>> pypiconfig.is_old_pypi_config()
    True
    >>> pypiconfig.is_new_pypi_config()
    True

If there's no multiple pypi support, a config file in the new format isn't
recognized as the new format as it cannot possibly function:

    >>> pypi.mupload = None
    >>> sys.version_info = (2, 5)
    >>> pypiconfig = pypi.PypiConfig(config_filename=pypirc_new)
    >>> pypiconfig.config == None # Format isn't right, so no config.
    True
    >>> pypiconfig.is_old_pypi_config()
    False
    >>> pypiconfig.is_new_pypi_config()
    False

If there's no collective.dist, there's also no list of distutils servers:

    >>> pypiconfig.distutils_servers()
    []

Reset the collective.dist again:

    >>> pypi.mupload = 'mock that collective.dist is available'

Now we can parse the new format again and query a list of distutils servers:

    >>> pypiconfig = pypi.PypiConfig(config_filename=pypirc_new)
    >>> from pprint import pprint
    >>> pprint(sorted(pypiconfig.distutils_servers()))
    ['mycompany', 'plone.org', 'pypi']

If we have an old config file, the distutils server list is also empty:

    >>> pypiconfig = pypi.PypiConfig(config_filename=pypirc_old)
    >>> pypiconfig.distutils_servers()
    []

And with a file with both an "old" and a "new" set of sections, the pypi entry
is filtered out of the distutils servers list as that's handled by the "old"
part:

    >>> pypiconfig = pypi.PypiConfig(config_filename=pypirc_both)
    >>> pprint(sorted(pypiconfig.distutils_servers()))
    ['local']


Asking for making a release or not
----------------------------------

Some people hardly ever want to make a full release of a package to
pypi; a subversion tag may be enough.  They can tell zest.releaser to
use a different default answer when it asks to make a checkout for a
release.  This means you can usually just press Enter on all questions
that zest.releaser asks.

We try to read a [zest.releaser] section, either in pypirc or
setup.cfg.  Currently we only check for a 'release' option.  We can
ask for the result like this, which by default is True:

    >>> pypiconfig = pypi.PypiConfig(config_filename=pypirc_both)
    >>> pypiconfig.want_release()
    True

We have a pypirc for this:

    >>> pypirc_no_release = pkg_resources.resource_filename(
    ...     'zest.releaser.tests', 'pypirc_no_release.txt')
    >>> pypiconfig = pypi.PypiConfig(config_filename=pypirc_no_release)
    >>> pypiconfig.want_release()
    False

We can also specify to always do that checkout during a release:

    >>> pypirc_yes_release = pkg_resources.resource_filename(
    ...     'zest.releaser.tests', 'pypirc_yes_release.txt')
    >>> pypiconfig = pypi.PypiConfig(config_filename=pypirc_yes_release)
    >>> pypiconfig.want_release()
    True


Fixing setup.cfg
----------------

A setup.cfg file can be used to influence the release process.  This
may contain options that are not advisable in the released package but
should only be used during development.  We can clean that up.  First
we prepare a directory.

    >>> pypi.SETUP_CONFIG_FILE
    'setup.cfg'
    >>> import os
    >>> import shutil
    >>> import tempfile
    >>> tempdir = tempfile.mkdtemp()
    >>> os.chdir(tempdir)

Without a setup.cfg there is no config:

    >>> setup_config = pypi.SetupConfig()
    >>> setup_config.config is None
    True
    >>> setup_config.has_bad_commands()
    False
    >>> setup_config.fix_config()
    >>> os.path.exists(pypi.SETUP_CONFIG_FILE)
    False

Now we add a setup.cfg with some good and some bad commands:

    >>> SETUP_CFG = """
    ... [zest.releaser]
    ... release = no
    ...
    ... [egg_info]
    ... tag_build = dev
    ... tag_svn_revision = true"""
    >>> open(pypi.SETUP_CONFIG_FILE, 'wb').write(SETUP_CFG)
    >>> setup_config = pypi.SetupConfig()
    >>> setup_config.config
    <ConfigParser.ConfigParser instance at ...>
    >>> setup_config.has_bad_commands()
    True

Fixing the config also prints the new config:

    >>> setup_config.fix_config()
    [zest.releaser]
    release = no
    <BLANKLINE>
    [egg_info]
    tag_build =
    tag_svn_revision = false
    >>> os.path.exists(pypi.SETUP_CONFIG_FILE)
    True
    >>> print ''.join(open(pypi.SETUP_CONFIG_FILE).readlines())
    [zest.releaser]
    release = no
    <BLANKLINE>
    [egg_info]
    tag_build =
    tag_svn_revision = false

We try that again with this fixed up config file as input:

    >>> setup_config = pypi.SetupConfig()
    >>> setup_config.config
    <ConfigParser.ConfigParser instance at ...>
    >>> setup_config.has_bad_commands()
    False
    >>> setup_config.fix_config()
    >>> os.path.exists(pypi.SETUP_CONFIG_FILE)
    True
    >>> print ''.join(open(pypi.SETUP_CONFIG_FILE).readlines())
    [zest.releaser]
    release = no
    <BLANKLINE>
    [egg_info]
    tag_build =
    tag_svn_revision = false

We are done with testing so we restore the original version:

    >>> sys.version_info = orig_version


No-input mode
-------------

In some cases you want no questions asked. Zest.releaser should just do its
job without asking for versions or confirmations. You can enable this
behaviour with a ``--no-input`` commandline option, but also by adding
``no-input = yes`` to the ``[zest.releaser]`` section in ``setup.cfg``.

The default is False:

    >>> setup_config.no_input()
    False

Enable the option:

    >>> SETUP_CFG = """
    ... [zest.releaser]
    ... no-input = yes
    ... """
    >>> open(pypi.SETUP_CONFIG_FILE, 'wb').write(SETUP_CFG)
    >>> setup_config = pypi.SetupConfig()
    >>> setup_config.no_input()
    True
