Skip to content

Isolate tests and run concurrently #1159

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 30 commits into from
Aug 27, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
bd127f8
Shadow the built in tmpdir fixture to use our Path object
dstufft Aug 22, 2013
17cfdc5
Refactor tests.lib into classes that can be used to modify state
dstufft Aug 22, 2013
f70b2a1
Use our classes from tests.lib.* in pytest fixtures
dstufft Aug 22, 2013
0c42245
Replace explicit calls to reset_env() with the script fixture
dstufft Aug 22, 2013
f9a3b97
Explicitly pass around a directory for tests.lib.local_repos
dstufft Aug 22, 2013
9bb20bd
Don't depend on relative file path for test data
dstufft Aug 22, 2013
c3f7a9c
Hide assert_all_changes from the traceback
dstufft Aug 22, 2013
d9c8452
Work around race conditions in the code
dstufft Aug 22, 2013
5827fd1
Use pytest-xdist to distribute the tests over multiple CPUs
dstufft Aug 22, 2013
5535300
Remove configuration for pip mirrors
dstufft Aug 22, 2013
45dc936
Define a hash seed for stable dictionary/set order on Python 3.3+
dstufft Aug 22, 2013
6b02bc7
Use monkeypatch to force shutil to not use the fd functions
dstufft Aug 22, 2013
9ed8c1a
Namespace the PipTestEnvironment inside the temporary directory
dstufft Aug 23, 2013
bacf3a0
Wrap the test data in a helper that will copy to the tmpdir
dstufft Aug 23, 2013
487925e
Isolate the tests from the shared data directory
dstufft Aug 23, 2013
cb73080
We already have the in development version installed in the venv
dstufft Aug 23, 2013
1bd8dd9
Determine the src directory automatically
dstufft Aug 23, 2013
1650d41
Fix some missing find_links -> data.find_links switches
dstufft Aug 23, 2013
fe60a1d
Use a constant for the source directory
dstufft Aug 23, 2013
99f520d
Instead of monkeypatching setuptools on Travis, just upgrade it
dstufft Aug 23, 2013
5fece08
Hide assert_raises_regexp in the traceback
dstufft Aug 24, 2013
946465b
The error message isn't always the same for this test
dstufft Aug 24, 2013
86f5ebb
Work around a Python 3.2 bug with pytest and pytest-xdist
dstufft Aug 24, 2013
3095571
Copy pip into the per test temporary location as well
dstufft Aug 24, 2013
1ba1c70
Use a hash seed of 0 to match the pre randomization value
dstufft Aug 24, 2013
4516a34
Add an extra newline
dstufft Aug 24, 2013
fab60dc
Do not use parallel tests on Python 3.2
dstufft Aug 24, 2013
0cc93cc
Bump up our concurrent tests to 16 instead of 8
dstufft Aug 24, 2013
67bd767
Use the verbose output on travis to better see the current state
dstufft Aug 24, 2013
b9706a7
16 processes seems to make PyPy very slow
dstufft Aug 24, 2013
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ before_install:
- echo -e "[web]\ncacerts = /etc/ssl/certs/ca-certificates.crt" >> ~/.hgrc
- git config --global user.email "[email protected]"
- git config --global user.name "Pip"
install: pip install pytest git+https://github.com/pypa/virtualenv@develop#egg=virtualenv scripttest>=1.3 mock
script: py.test
install:
- pip install --upgrade setuptools
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool, this actually works on travis.

- pip install pytest pytest-xdist git+https://github.com/pypa/virtualenv@develop#egg=virtualenv scripttest>=1.3 mock
script:
- "if [[ $TRAVIS_PYTHON_VERSION == '3.2' ]]; then py.test -v; fi"
- "if [[ $TRAVIS_PYTHON_VERSION != '3.2' ]]; then py.test -v -n 8; fi"
notifications:
irc: "irc.freenode.org#pip"
branches:
Expand All @@ -20,4 +24,4 @@ branches:
- 1.3.X
- 1.4.X
env:
- PIP_USE_MIRRORS=true
- PYTHONHASHSEED=0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where does this apply? 0?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Python 3.3+ hash randomization is on by default, this confuses pytest-xdist. Setting the hash seed to zero effectively disables hash randomization.

See https://bitbucket.org/hpk42/pytest/issue/346/pytest-xdist-and-python-33-is-sort-of

94 changes: 94 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import shutil

import py
import pytest

from tests.lib import SRC_DIR, TestData
from tests.lib.path import Path
from tests.lib.scripttest import PipTestEnvironment
from tests.lib.venv import VirtualEnvironment


@pytest.fixture
def tmpdir(request):
"""
Return a temporary directory path object which is unique to each test
function invocation, created as a sub directory of the base temporary
directory. The returned object is a ``tests.lib.path.Path`` object.

This is taken from pytest itself but modified to return our typical
path object instead of py.path.local.
"""
name = request.node.name
name = py.std.re.sub("[\W]", "_", name)
tmp = request.config._tmpdirhandler.mktemp(name, numbered=True)
return Path(tmp)


@pytest.fixture
def virtualenv(tmpdir, monkeypatch):
"""
Return a virtual environment which is unique to each test function
invocation created inside of a sub directory of the test function's
temporary directory. The returned object is a
``tests.lib.venv.VirtualEnvironment`` object.
"""
# Force shutil to use the older method of rmtree that didn't use the fd
# functions. These seem to fail on Travis (and only on Travis).
monkeypatch.setattr(shutil, "_use_fd_functions", False, raising=False)

# Copy over our source tree so that each virtual environment is self
# contained
pip_src = tmpdir.join("pip_src").abspath
shutil.copytree(SRC_DIR, pip_src,
ignore=shutil.ignore_patterns(
"*.pyc", "docs/", "tests/", "pip.egg-info",
),
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wondering if we should build a wheel and install from that over and over.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure. I did try building a sdist and installing from it and the increase in time was pretty noticeable. Right now we copy the directory into the temporary directory and then using python setup.py develop to install it. I'd be for installing from a wheel though assuming it didn't hurt test run time too much.


# Create the virtual environment
venv = VirtualEnvironment.create(
tmpdir.join("workspace", "venv"),
pip_source_dir=pip_src,
)

# Undo our monkeypatching of shutil
monkeypatch.undo()

return venv


@pytest.fixture
def script(tmpdir, virtualenv):
"""
Return a PipTestEnvironment which is unique to each test function and
will execute all commands inside of the unique virtual environment for this
test function. The returned object is a
``tests.lib.scripttest.PipTestEnvironment``.
"""
return PipTestEnvironment(
# The base location for our test environment
tmpdir.join("workspace"),

# Tell the Test Environment where our virtualenv is located
virtualenv=virtualenv.location,

# Do not ignore hidden files, they need to be checked as well
ignore_hidden=False,

# We are starting with an already empty directory
start_clear=False,

# We want to ensure no temporary files are left behind, so the
# PipTestEnvironment needs to capture and assert against temp
capture_temp=True,
assert_no_temp=True,
)


@pytest.fixture
def data(tmpdir):
return TestData.copy(tmpdir.join("data"))

# This is here to work around a bug with pytest, pytest-xdist, and Python 3.2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extra new lines and the comment itself.


18 changes: 7 additions & 11 deletions tests/functional/test_bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,22 @@
import textwrap
from os.path import abspath, exists, join
from pip.download import path_to_url2
from tests.lib import tests_data, reset_env
from tests.lib.path import Path
from tests.lib.local_repos import local_checkout


def test_create_bundle():
def test_create_bundle(script, tmpdir, data):
"""
Test making a bundle. We'll grab one package from the filesystem
(the FSPkg dummy package), one from vcs (initools) and one from an
index (pip itself).

"""
script = reset_env()
fspkg = path_to_url2(Path(tests_data)/'packages'/'FSPkg')
fspkg = path_to_url2(data.packages/'FSPkg')
script.pip('install', '-e', fspkg)
pkg_lines = textwrap.dedent('''\
-e %s
-e %s#egg=initools-dev
pip''' % (fspkg, local_checkout('svn+http://svn.colorstudy.com/INITools/trunk')))
pip''' % (fspkg, local_checkout('svn+http://svn.colorstudy.com/INITools/trunk', tmpdir.join("cache"))))
script.scratch_path.join("bundle-req.txt").write(pkg_lines)
# Create a bundle in env.scratch_path/ test.pybundle
result = script.pip('bundle', '-r', script.scratch_path/ 'bundle-req.txt', script.scratch_path/ 'test.pybundle')
Expand All @@ -33,29 +30,28 @@ def test_create_bundle():
assert 'build/pip/' in files


def test_cleanup_after_create_bundle():
def test_cleanup_after_create_bundle(script, tmpdir, data):
"""
Test clean up after making a bundle. Make sure (build|src)-bundle/ dirs are removed but not src/.

"""
script = reset_env()
# Install an editable to create a src/ dir.
args = ['install']
args.extend(['-e',
'%s#egg=pip-test-package' %
local_checkout('git+http://github.com/pypa/pip-test-package.git')])
local_checkout('git+http://github.com/pypa/pip-test-package.git', tmpdir.join("cache"))])
script.pip(*args)
build = script.venv_path/"build"
src = script.venv_path/"src"
assert not exists(build), "build/ dir still exists: %s" % build
assert exists(src), "expected src/ dir doesn't exist: %s" % src

# Make the bundle.
fspkg = 'file://%s/FSPkg' %join(tests_data, 'packages')
fspkg = path_to_url2(data.packages/'FSPkg')
pkg_lines = textwrap.dedent('''\
-e %s
-e %s#egg=initools-dev
pip''' % (fspkg, local_checkout('svn+http://svn.colorstudy.com/INITools/trunk')))
pip''' % (fspkg, local_checkout('svn+http://svn.colorstudy.com/INITools/trunk', tmpdir.join("cache"))))
script.scratch_path.join("bundle-req.txt").write(pkg_lines)
script.pip('bundle', '-r', 'bundle-req.txt', 'test.pybundle')
build_bundle = script.scratch_path/"build-bundle"
Expand Down
36 changes: 15 additions & 21 deletions tests/functional/test_completion.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import os
from tests.lib import reset_env


def test_completion_for_bash():
def test_completion_for_bash(script):
"""
Test getting completion for bash shell
"""
script = reset_env()
bash_completion = """\
_pip_completion()
{
Expand All @@ -20,11 +18,10 @@ def test_completion_for_bash():
assert bash_completion in result.stdout, 'bash completion is wrong'


def test_completion_for_zsh():
def test_completion_for_zsh(script):
"""
Test getting completion for zsh shell
"""
script = reset_env()
zsh_completion = """\
function _pip_completion {
local words cword
Expand All @@ -40,32 +37,29 @@ def test_completion_for_zsh():
assert zsh_completion in result.stdout, 'zsh completion is wrong'


def test_completion_for_unknown_shell():
def test_completion_for_unknown_shell(script):
"""
Test getting completion for an unknown shell
"""
script = reset_env()
error_msg = 'no such option: --myfooshell'
result = script.pip('completion', '--myfooshell', expect_error=True)
assert error_msg in result.stderr, 'tests for an unknown shell failed'


def test_completion_alone():
def test_completion_alone(script):
"""
Test getting completion for none shell, just pip completion
"""
script = reset_env()
result = script.pip('completion', expect_error=True)
assert 'ERROR: You must pass --bash or --zsh' in result.stderr, \
'completion alone failed -- ' + result.stderr


def setup_completion(words, cword):
environ = os.environ.copy()
script = reset_env(environ)
environ['PIP_AUTO_COMPLETE'] = '1'
environ['COMP_WORDS'] = words
environ['COMP_CWORD'] = cword
def setup_completion(script, words, cword):
script.environ = os.environ.copy()
script.environ['PIP_AUTO_COMPLETE'] = '1'
script.environ['COMP_WORDS'] = words
script.environ['COMP_CWORD'] = cword

# expect_error is True because autocomplete exists with 1 status code
result = script.run('python', '-c', 'import pip;pip.autocomplete()',
Expand All @@ -74,31 +68,31 @@ def setup_completion(words, cword):
return result, script


def test_completion_for_un_snippet():
def test_completion_for_un_snippet(script):
"""
Test getting completion for ``un`` should return
uninstall and unzip
"""

res, env = setup_completion('pip un', '1')
res, env = setup_completion(script, 'pip un', '1')
assert res.stdout.strip().split() == ['uninstall', 'unzip'], res.stdout


def test_completion_for_default_parameters():
def test_completion_for_default_parameters(script):
"""
Test getting completion for ``--`` should contain --help
"""

res, env = setup_completion('pip --', '1')
res, env = setup_completion(script, 'pip --', '1')
assert '--help' in res.stdout,\
"autocomplete function could not complete ``--``"


def test_completion_option_for_command():
def test_completion_option_for_command(script):
"""
Test getting completion for ``--`` in command (eg. pip search --)
"""

res, env = setup_completion('pip search --', '2')
res, env = setup_completion(script, 'pip search --', '2')
assert '--help' in res.stdout,\
"autocomplete function could not complete ``--``"
Loading