Skip to content

Commit

Permalink
test(python): use constraints files to check dependency lower bounds (#…
Browse files Browse the repository at this point in the history
…869)

Use a constraints file when installing dependencies for system and unit tests nox sessions.

https://pip.pypa.io/en/stable/user_guide/#constraints-files
> Constraints files are requirements files that **only control which version of a requirement is installed, not whether it is installed or not**. Their syntax and contents is nearly identical to Requirements Files. There is one key difference: Including a package in a constraints file does not trigger installation of the package.

```
testing
├── constraints-3.10.txt
├── constraints-3.11.txt
├── constraints-3.6.txt
├── constraints-3.7.txt
├── constraints-3.8.txt
└── constraints-3.9.txt
```
  
Going forward, one constraints file (currently 3.6) will be populated with every library requirement and extra listed in the `setup.py`. The constraints file will pin each requirement to the lower bound. This ensures that library maintainers will see test failures if they forget to update a lower bound on a dependency.

See googleapis/python-bigquery#263 for an example
  • Loading branch information
busunkim96 committed Mar 23, 2021
1 parent f5c5904 commit 86ed43d
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 23 deletions.
33 changes: 21 additions & 12 deletions synthtool/gcp/templates/python_library/noxfile.py.j2
Expand Up @@ -18,6 +18,7 @@

from __future__ import absolute_import
import os
import pathlib
import shutil

import nox
Expand All @@ -30,6 +31,8 @@ DEFAULT_PYTHON_VERSION="{{ default_python_version }}"
SYSTEM_TEST_PYTHON_VERSIONS=[{% for v in system_test_python_versions %}"{{v}}"{% if not loop.last %},{% endif %}{% endfor %}]
UNIT_TEST_PYTHON_VERSIONS=[{% for v in unit_test_python_versions %}"{{v}}"{% if not loop.last %},{% endif %}{% endfor %}]

CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute()

# 'docfx' is excluded since it only needs to run in 'docs-presubmit'
nox.options.sessions = [
"unit",
Expand Down Expand Up @@ -88,24 +91,27 @@ def lint_setup_py(session):
def default(session):
# Install all test dependencies, then install this package in-place.

constraints_path = str(
CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt"
)
{%- if microgenerator %}
session.install("asyncmock", "pytest-asyncio")
session.install("asyncmock", "pytest-asyncio", "-c", constraints_path)
{% endif %}
session.install("mock", "pytest", "pytest-cov", {% for d in unit_test_external_dependencies %}"{{d}}"{% if not loop.last %},{% endif %}{% endfor %})
{% for dependency in unit_test_local_dependencies %}session.install("-e", "{{dependency}}")
session.install("mock", "pytest", "pytest-cov", {% for d in unit_test_external_dependencies %}"{{d}}",{% endfor %} "-c", constraints_path)
{% for dependency in unit_test_local_dependencies %}session.install("-e", "{{dependency}}", "-c", constraints_path)
{% endfor %}
{% for dependency in unit_test_dependencies %}session.install("-e", "{{dependency}}"){% endfor %}
{% for dependency in unit_test_dependencies %}session.install("-e", "{{dependency}}", "-c", constraints_path){% endfor %}
{%- if unit_test_extras_by_python %}
{% for extras_python in unit_test_extras_by_python %}
{%- if not loop.first %}el{% endif %}if session.python == "{{extras_python}}":
extras = "[{{",".join(unit_test_extras_by_python[extras_python])}}]"
{% endfor %}else:
extras = "{%- if unit_test_extras %}[{{",".join(unit_test_extras)}}]{% endif %}"
session.install("-e", f".{extras}")
session.install("-e", f".{extras}", "-c", constraints_path)
{% elif unit_test_extras %}
session.install("-e", ".[{{",".join(unit_test_extras)}}]")
session.install("-e", ".[{{",".join(unit_test_extras)}}]", "-c", constraints_path)
{% else %}
session.install("-e", ".")
session.install("-e", ".", "-c", constraints_path)
{% endif %}

# Run py.test against the unit tests.
Expand All @@ -132,6 +138,9 @@ def unit(session):
@nox.session(python=SYSTEM_TEST_PYTHON_VERSIONS)
def system(session):
"""Run the system test suite."""
constraints_path = str(
CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt"
)
system_test_path = os.path.join("tests", "system.py")
system_test_folder_path = os.path.join("tests", "system")

Expand All @@ -156,10 +165,10 @@ def system(session):

# Install all test dependencies, then install this package into the
# virtualenv's dist-packages.
session.install("mock", "pytest", "google-cloud-testutils", {% for d in system_test_external_dependencies %}"{{d}}"{% if not loop.last %},{% endif %}{% endfor %})
session.install("mock", "pytest", "google-cloud-testutils"{% for d in system_test_external_dependencies %}, "{{d}}"{% endfor %}, "-c", constraints_path)

{%- if system_test_local_dependencies %}
{% for dependency in system_test_local_dependencies %}session.install("-e", "{{dependency}}")
{% for dependency in system_test_local_dependencies %}session.install("-e", "{{dependency}}", "-c", constraints_path)
{% endfor %}
{%- endif %}
{%- if system_test_extras_by_python %}
Expand All @@ -168,11 +177,11 @@ def system(session):
extras = "[{{",".join(system_test_extras_by_python[extras_python])}}]"
{% endfor %}else:
extras = "{%- if system_test_extras %}[{{",".join(system_test_extras)}}]{% endif %}"
session.install("-e", f".{extras}")
session.install("-e", f".{extras}", "-c", constraints_path)
{% elif system_test_extras %}
session.install("-e", ".[{{",".join(system_test_extras)}}]")
session.install("-e", ".[{{",".join(system_test_extras)}}]", "-c", constraints_path)
{% else %}
session.install("-e", ".")
session.install("-e", ".", "-c", constraints_path)
{% endif %}

# Run py.test against the system tests.
Expand Down
22 changes: 11 additions & 11 deletions tests/test_python_library.py
Expand Up @@ -27,43 +27,43 @@
@pytest.mark.parametrize(
["template_kwargs", "expected_text"],
[
({}, ["import nox", 'session.install("-e", ".")']),
({}, ["import nox", 'session.install("-e", ".", "-c", constraints_path)']),
(
{"unit_test_local_dependencies": ["../testutils", "../unitutils"]},
[
'session.install("-e", "../testutils")',
'session.install("-e", "../unitutils")',
'session.install("-e", "../testutils", "-c", constraints_path)',
'session.install("-e", "../unitutils", "-c", constraints_path)',
],
),
(
{"system_test_local_dependencies": ["../testutils", "../sysutils"]},
[
'session.install("-e", "../testutils")',
'session.install("-e", "../sysutils")',
'session.install("-e", "../testutils", "-c", constraints_path)',
'session.install("-e", "../sysutils", "-c", constraints_path)',
],
),
(
{"unit_test_extras": ["abc", "def"]},
['session.install("-e", ".[abc,def]")'],
['session.install("-e", ".[abc,def]", "-c", constraints_path)'],
),
(
{"system_test_extras": ["abc", "def"]},
['session.install("-e", ".[abc,def]")'],
['session.install("-e", ".[abc,def]", "-c", constraints_path)'],
),
(
{"unit_test_extras_by_python": {"3.8": ["abc", "def"]}},
[
'if session.python == "3.8":\n extras = "[abc,def]"',
'else:\n extras = ""',
'session.install("-e", f".{extras}")',
'session.install("-e", f".{extras}", "-c", constraints_path)',
],
),
(
{"system_test_extras_by_python": {"3.8": ["abc", "def"]}},
[
'if session.python == "3.8":\n extras = "[abc,def]"',
'else:\n extras = ""',
'session.install("-e", f".{extras}")',
'session.install("-e", f".{extras}", "-c", constraints_path)',
],
),
(
Expand All @@ -74,7 +74,7 @@
[
'if session.python == "3.8":\n extras = "[abc,def]"',
'else:\n extras = "[tuv,wxyz]"',
'session.install("-e", f".{extras}")',
'session.install("-e", f".{extras}", "-c", constraints_path)',
],
),
(
Expand All @@ -85,7 +85,7 @@
[
'if session.python == "3.8":\n extras = "[abc,def]"',
'else:\n extras = "[tuv,wxyz]"',
'session.install("-e", f".{extras}")',
'session.install("-e", f".{extras}", "-c", constraints_path)',
],
),
],
Expand Down

0 comments on commit 86ed43d

Please sign in to comment.