Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
53 changes: 35 additions & 18 deletions scripts/run_unit_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@
import subprocess
import sys

from script_utils import check_output
from script_utils import get_changed_packages
from script_utils import in_travis
from script_utils import in_travis_pr
from script_utils import travis_branch


PROJECT_ROOT = os.path.abspath(
os.path.join(os.path.dirname(__file__), '..'))
Expand All @@ -44,24 +50,6 @@
UNSET_SENTINEL = object() # Sentinel for argparser


def check_output(*args):
"""Run a command on the operation system.

:type args: tuple
:param args: Keyword arguments to pass to ``subprocess.check_output``.

:rtype: str
:returns: The raw STDOUT from the command (converted from bytes
if necessary).
"""
cmd_output = subprocess.check_output(args)
# On Python 3, this returns bytes (from STDOUT), so we
# convert to a string.
cmd_output = cmd_output.decode('utf-8')
# Also strip the output since it usually has a trailing newline.
return cmd_output.strip()


def get_package_directories():
"""Get a list of directories containing sub-packages.

Expand All @@ -83,6 +71,30 @@ def get_package_directories():
return result


def get_travis_directories(package_list):
"""Get list of packages that need to be tested on Travis CI.

See: https://travis-ci.com/

If the current Travis build is for a pull request (PR), this will
limit the directories to the ones impacted by the PR. Otherwise
it will just test all package directories.

:type package_list: list
:param package_list: The list of **all** valid packages with unit tests.

:rtype: list
:returns: A list of all package directories where tests
need to be run.
"""
if in_travis_pr():
pr_against_branch = travis_branch()
return get_changed_packages('HEAD', pr_against_branch,
package_list)
else:
return package_list


def verify_packages(subset, all_packages):
"""Verify that a subset of packages are among all packages.

Expand All @@ -107,6 +119,9 @@ def get_test_packages():
Filters the package list in the following order:

* Check command line for packages passed in as positional arguments
* Check if in Travis, then limit the subset based on changes
in a Pull Request ("push" builds to branches may not have
any filtering)
* Just use all packages

:rtype: list
Expand All @@ -120,6 +135,8 @@ def get_test_packages():
if args.packages is not UNSET_SENTINEL:
verify_packages(args.packages, all_packages)
return sorted(args.packages)
elif in_travis():
return get_travis_directories(all_packages)
else:
return all_packages

Expand Down
78 changes: 77 additions & 1 deletion scripts/script_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"""Common helpers for testing scripts."""

import os
import subprocess


LOCAL_REMOTE_ENV = 'GOOGLE_CLOUD_TESTING_REMOTE'
Expand Down Expand Up @@ -81,5 +82,80 @@ def travis_branch():
:rtype: str
:returns: The name of the branch the current pull request is
changed against.
:raises: :class:`~exceptions.OSError` if the ``TRAVIS_BRANCH_ENV``
environment variable isn't set during a pull request
build.
"""
return os.getenv(TRAVIS_BRANCH_ENV)
try:
return os.environ[TRAVIS_BRANCH_ENV]
except KeyError:
msg = ('Pull request build does not have an '
'associated branch set (via %s)') % (TRAVIS_BRANCH_ENV,)
raise OSError(msg)


def check_output(*args):
"""Run a command on the operation system.

:type args: tuple
:param args: Arguments to pass to ``subprocess.check_output``.

:rtype: str
:returns: The raw STDOUT from the command (converted from bytes
if necessary).
"""
cmd_output = subprocess.check_output(args)
# On Python 3, this returns bytes (from STDOUT), so we
# convert to a string.
cmd_output = cmd_output.decode('utf-8')
# Also strip the output since it usually has a trailing newline.
return cmd_output.strip()


def rootname(filename):
"""Get the root directory that a file is contained in.

:type filename: str
:param filename: The path / name of a file.

:rtype: str
:returns: The root directory containing the file.
"""
if os.path.sep not in filename:
return ''
else:
file_root, _ = filename.split(os.path.sep, 1)
return file_root


def get_changed_packages(blob_name1, blob_name2, package_list):
"""Get a list of packages which have changed between two changesets.

:type blob_name1: str
:param blob_name1: The name of a commit hash or branch name or other
``git`` artifact.

:type blob_name2: str
:param blob_name2: The name of a commit hash or branch name or other
``git`` artifact.

:type package_list: list
:param package_list: The list of **all** valid packages with unit tests.

:rtype: list
:returns: A list of all package directories that have changed
between ``blob_name1`` and ``blob_name2``. Starts
with a list of valid packages (``package_list``)
and filters out the unchanged directories.
"""
changed_files = check_output(
'git', 'diff', '--name-only', blob_name1, blob_name2)
changed_files = changed_files.split('\n')

result = set()
for filename in changed_files:
file_root = rootname(filename)
if file_root in package_list:
result.add(file_root)

return sorted(result)
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ skip_install =
py34: True
py35: True
isolated-cover: True
passenv = TRAVIS*

[testenv:isolated-cover]
commands =
Expand Down