Skip to content

Commit

Permalink
Merge pull request #2568 from JacobHayes/make-local-test-easy
Browse files Browse the repository at this point in the history
Simplify testing for new contributors
  • Loading branch information
uranusjr committed Jul 12, 2018
2 parents 6cfe5fb + f711678 commit 9460500
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 37 deletions.
1 change: 0 additions & 1 deletion .env
@@ -1,2 +1 @@
HELLO=WORLD
PYPI_VENDOR_DIR="./tests/pypi/"
42 changes: 42 additions & 0 deletions CONTRIBUTING.md
Expand Up @@ -55,3 +55,45 @@ Please be aware of the following things when filing bug reports:
If you do not provide all of these things, it will take us much longer to
fix your problem. If we ask you to clarify these and you never respond, we
will close your issue without fixing it.

## Development Setup

To get your development environment setup, run:

```sh
pip install -e .
pipenv install --dev
```

This will install the repo version of Pipenv and then install the development
dependencies. Once that has completed, you can start developing.

The repo version of Pipenv must be installed over other global versions to
resolve conflicts with the `pipenv` folder being implicitly added to `sys.path`.
See pypa/pipenv#2557 for more details.

### Testing

Tests are written in `pytest` style and can be run very simply:

```sh
pytest
```

This will run all Pipenv tests, which can take awhile. To run a subset of the
tests, the standard pytest filters are available, such as:

- provide a directory or file: `pytest tests/unit` or `pytest tests/unit/test_cmdparse.py`
- provide a keyword expression: `pytest -k test_lock_editable_vcs_without_install`
- provide a nodeid: `pytest tests/unit/test_cmdparse.py::test_parse`
- provide a test marker: `pytest -m lock`

#### Package Index

To speed up testing, tests that rely on a package index for locking and
installing use a local server that contains vendored packages in the
`tests/pypi` directory. Each vendored package should have it's own folder
containing the necessary releases. When adding a release for a package, it is
easiest to use either the `.tar.gz` or universal wheels (ex: `py2.py3-none`). If
a `.tar.gz` or universal wheel is not available, add wheels for all available
architectures and platforms.
3 changes: 2 additions & 1 deletion pytest.ini
@@ -1,3 +1,4 @@
[pytest]
addopts = -n auto
norecursedirs = vendor patched
; Add vendor and patched in addition to the default list of ignored dirs
norecursedirs = .* build dist CVS _darcs {arch} *.egg vendor patched
2 changes: 1 addition & 1 deletion run-tests.bat
Expand Up @@ -4,4 +4,4 @@ virtualenv R:\.venv
R:\.venv\Scripts\pip install -e . --upgrade --upgrade-strategy=only-if-needed
R:\.venv\Scripts\pipenv install --dev

SET RAM_DISK=R:&& SET PYPI_VENDOR_DIR=".\tests\pypi\" && R:\.venv\Scripts\pipenv run pytest -n auto -v tests --tap-stream > report.tap
SET RAM_DISK=R: && R:\.venv\Scripts\pipenv run pytest -n auto -v tests --tap-stream > report.tap
3 changes: 0 additions & 3 deletions run-tests.sh
Expand Up @@ -4,9 +4,6 @@

set -eo pipefail

# Set the PYPI vendor URL for pytest-pypi.
PYPI_VENDOR_DIR="$(pwd)/tests/pypi/"
export PYPI_VENDOR_DIR
export PYTHONIOENCODING="utf-8"
export LANG=C.UTF-8

Expand Down
4 changes: 3 additions & 1 deletion tests/integration/conftest.py
Expand Up @@ -9,6 +9,7 @@
from pipenv.vendor import requests
from pipenv.vendor import six
from pipenv.vendor import toml
from pytest_pypi.app import prepare_packages as prepare_pypi_packages

if six.PY2:
class ResourceWarning(Warning):
Expand All @@ -30,6 +31,8 @@ def check_internet():
WE_HAVE_INTERNET = check_internet()

TESTS_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PYPI_VENDOR_DIR = os.path.join(TESTS_ROOT, 'pypi')
prepare_pypi_packages(PYPI_VENDOR_DIR)


def pytest_runtest_setup(item):
Expand Down Expand Up @@ -68,7 +71,6 @@ def __enter__(self):
os.environ['PIPENV_DONT_USE_PYENV'] = '1'
os.environ['PIPENV_IGNORE_VIRTUALENVS'] = '1'
os.environ['PIPENV_VENV_IN_PROJECT'] = '1'
os.environ['PYPI_VENDOR_DIR'] = os.path.join(TESTS_ROOT, 'pypi')
if self.chdir:
os.chdir(self.path)
return self
Expand Down
10 changes: 8 additions & 2 deletions tests/integration/test_install_basic.py
Expand Up @@ -359,10 +359,16 @@ def test_install_venv_project_directory(PipenvInstance, pypi):
os.environ["WORKON_HOME"] = workon_home.name
if "PIPENV_VENV_IN_PROJECT" in os.environ:
del os.environ["PIPENV_VENV_IN_PROJECT"]

c = p.pipenv("install six")
assert c.return_code == 0
project = Project()
assert Path(project.virtualenv_location).joinpath(".project").exists()

venv_loc = None
for line in c.err.splitlines():
if line.startswith("Virtualenv location:"):
venv_loc = Path(line.split(":", 1)[-1].strip())
assert venv_loc is not None
assert venv_loc.joinpath(".project").exists()


@pytest.mark.deploy
Expand Down
59 changes: 33 additions & 26 deletions tests/pytest-pypi/pytest_pypi/app.py
Expand Up @@ -4,51 +4,54 @@
import requests
from flask import Flask, redirect, abort, render_template, send_file, jsonify

PYPI_VENDOR_DIR = os.environ.get('PYPI_VENDOR_DIR', './pypi')
PYPI_VENDOR_DIR = os.path.abspath(PYPI_VENDOR_DIR)

app = Flask(__name__)
session = requests.Session()

packages = {}


class Package(object):
"""docstring for Package"""
"""Package represents a collection of releases from one or more directories"""

def __init__(self, name):
super(Package, self).__init__()
self.name = name
self._releases = []
self.releases = {}
self._package_dirs = set()

@property
def releases(self):
r = []
for release in self._releases:
release = release[len(PYPI_VENDOR_DIR):].replace('\\', '/')
r.append(release)
return r
def json(self):
for path in self._package_dirs:
try:
with open(os.path.join(path, 'api.json')) as f:
return json.load(f)
except FileNotFoundError:
pass

def __repr__(self):
return "<Package name={0!r} releases={1!r}".format(self.name, len(self.releases))

def add_release(self, path_to_binary):
self._releases.append(path_to_binary)


def prepare_packages():
for root, dirs, files in os.walk(os.path.abspath(PYPI_VENDOR_DIR)):
path_to_binary = os.path.abspath(path_to_binary)
path, release = os.path.split(path_to_binary)
self.releases[release] = path_to_binary
self._package_dirs.add(path)


def prepare_packages(path):
"""Add packages in path to the registry."""
path = os.path.abspath(path)
if not (os.path.exists(path) and os.path.isdir(path)):
raise ValueError("{} is not a directory!".format(path))
for root, dirs, files in os.walk(path):
for file in files:
if not file.startswith('.') and not file.endswith('.json'):
package_name = root.split(os.path.sep)[-1]
package_name = os.path.basename(root)

if package_name not in packages:
packages[package_name] = Package(package_name)

packages[package_name].add_release(os.path.sep.join([root, file]))


prepare_packages()
packages[package_name].add_release(os.path.join(root, file))


@app.route('/')
Expand All @@ -74,22 +77,26 @@ def serve_package(package, release):
if package in packages:
package = packages[package]

for _release in package.releases:
if _release.endswith(release):
return send_file(os.path.sep.join([PYPI_VENDOR_DIR, _release]))
if release in package.releases:
return send_file(package.releases[release])

abort(404)


@app.route('/pypi/<package>/json')
def json_for_package(package):
try:
with open(os.path.sep.join([PYPI_VENDOR_DIR, package, 'api.json'])) as f:
return jsonify(json.load(f))
return jsonify(packages[package].json)
except Exception:
pass

r = session.get('https://pypi.org/pypi/{0}/json'.format(package))
return jsonify(r.json())


if __name__ == '__main__':
PYPI_VENDOR_DIR = os.environ.get('PYPI_VENDOR_DIR', './pypi')
PYPI_VENDOR_DIR = os.path.abspath(PYPI_VENDOR_DIR)
prepare_packages(PYPI_VENDOR_DIR)

app.run()
4 changes: 2 additions & 2 deletions tests/pytest-pypi/pytest_pypi/templates/package.html
Expand Up @@ -7,8 +7,8 @@
<body>
<h1>Links for {{ package.name }}</h1>
{% for release in package.releases %}
<a href="{{ release }}">{{ release }}</a>
<a href="/{{ package.name }}/{{ release }}">{{ release }}</a>
<br>
{% endfor %}
</body>
</html>
</html>

0 comments on commit 9460500

Please sign in to comment.