diff options
Diffstat (limited to 'system_tests/noxfile.py')
-rw-r--r-- | system_tests/noxfile.py | 498 |
1 files changed, 498 insertions, 0 deletions
diff --git a/system_tests/noxfile.py b/system_tests/noxfile.py new file mode 100644 index 0000000..459b71c --- /dev/null +++ b/system_tests/noxfile.py @@ -0,0 +1,498 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Noxfile for automating system tests. + +This file handles setting up environments needed by the system tests. This +separates the tests from their environment configuration. + +See the `nox docs`_ for details on how this file works: + +.. _nox docs: http://nox.readthedocs.io/en/latest/ +""" + +import os +import subprocess + +from nox.command import which +import nox +import py.path + +HERE = os.path.abspath(os.path.dirname(__file__)) +LIBRARY_DIR = os.path.abspath(os.path.dirname(HERE)) +DATA_DIR = os.path.join(HERE, "data") +SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, "service_account.json") +AUTHORIZED_USER_FILE = os.path.join(DATA_DIR, "authorized_user.json") +EXPLICIT_CREDENTIALS_ENV = "GOOGLE_APPLICATION_CREDENTIALS" +EXPLICIT_PROJECT_ENV = "GOOGLE_CLOUD_PROJECT" +EXPECT_PROJECT_ENV = "EXPECT_PROJECT_ID" + +SKIP_GAE_TEST_ENV = "SKIP_APP_ENGINE_SYSTEM_TEST" +GAE_APP_URL_TMPL = "https://{}-dot-{}.appspot.com" +GAE_TEST_APP_SERVICE = "google-auth-system-tests" + +# The download location for the Cloud SDK +CLOUD_SDK_DIST_FILENAME = "google-cloud-sdk.tar.gz" +CLOUD_SDK_DOWNLOAD_URL = "https://dl.google.com/dl/cloudsdk/release/{}".format( + CLOUD_SDK_DIST_FILENAME +) + +# This environment variable is recognized by the Cloud SDK and overrides +# the location of the SDK's configuration files (which is usually at +# ${HOME}/.config). +CLOUD_SDK_CONFIG_ENV = "CLOUDSDK_CONFIG" + +# If set, this is where the environment setup will install the Cloud SDK. +# If unset, it will download the SDK to a temporary directory. +CLOUD_SDK_ROOT = os.environ.get("CLOUD_SDK_ROOT") + +if CLOUD_SDK_ROOT is not None: + CLOUD_SDK_ROOT = py.path.local(CLOUD_SDK_ROOT) + CLOUD_SDK_ROOT.ensure(dir=True) # Makes sure the directory exists. +else: + CLOUD_SDK_ROOT = py.path.local.mkdtemp() + +# The full path the cloud sdk install directory +CLOUD_SDK_INSTALL_DIR = CLOUD_SDK_ROOT.join("google-cloud-sdk") + +# The full path to the gcloud cli executable. +GCLOUD = str(CLOUD_SDK_INSTALL_DIR.join("bin", "gcloud")) + +# gcloud requires Python 2 and doesn't work on 3, so we need to tell it +# where to find 2 when we're running in a 3 environment. +CLOUD_SDK_PYTHON_ENV = "CLOUDSDK_PYTHON" +CLOUD_SDK_PYTHON = which("python2", None) + +# Cloud SDK helpers + + +def install_cloud_sdk(session): + """Downloads and installs the Google Cloud SDK.""" + # Configure environment variables needed by the SDK. + # This sets the config root to the tests' config root. This prevents + # our tests from clobbering a developer's configuration when running + # these tests locally. + session.env[CLOUD_SDK_CONFIG_ENV] = str(CLOUD_SDK_ROOT) + # This tells gcloud which Python interpreter to use (always use 2.7) + session.env[CLOUD_SDK_PYTHON_ENV] = CLOUD_SDK_PYTHON + # This set the $PATH for the subprocesses so they can find the gcloud + # executable. + session.env["PATH"] = ( + str(CLOUD_SDK_INSTALL_DIR.join("bin")) + os.pathsep + os.environ["PATH"] + ) + + # If gcloud cli executable already exists, just update it. + if py.path.local(GCLOUD).exists(): + session.run(GCLOUD, "components", "update", "-q") + return + + tar_path = CLOUD_SDK_ROOT.join(CLOUD_SDK_DIST_FILENAME) + + # Download the release. + session.run("wget", CLOUD_SDK_DOWNLOAD_URL, "-O", str(tar_path), silent=True) + + # Extract the release. + session.run("tar", "xzf", str(tar_path), "-C", str(CLOUD_SDK_ROOT)) + session.run(tar_path.remove) + + # Run the install script. + session.run( + str(CLOUD_SDK_INSTALL_DIR.join("install.sh")), + "--usage-reporting", + "false", + "--path-update", + "false", + "--command-completion", + "false", + silent=True, + ) + + +def copy_credentials(credentials_path): + """Copies credentials into the SDK root as the application default + credentials.""" + dest = CLOUD_SDK_ROOT.join("application_default_credentials.json") + if dest.exists(): + dest.remove() + py.path.local(credentials_path).copy(dest) + + +def configure_cloud_sdk(session, application_default_credentials, project=False): + """Installs and configures the Cloud SDK with the given application default + credentials. + + If project is True, then a project will be set in the active config. + If it is false, this will ensure no project is set. + """ + install_cloud_sdk(session) + + # Setup the service account as the default user account. This is + # needed for the project ID detection to work. Note that this doesn't + # change the application default credentials file, which is user + # credentials instead of service account credentials sometimes. + session.run( + GCLOUD, "auth", "activate-service-account", "--key-file", SERVICE_ACCOUNT_FILE + ) + + if project: + session.run(GCLOUD, "config", "set", "project", "example-project") + else: + session.run(GCLOUD, "config", "unset", "project") + + # Copy the credentials file to the config root. This is needed because + # unfortunately gcloud doesn't provide a clean way to tell it to use + # a particular set of credentials. However, this does verify that gcloud + # also considers the credentials valid by calling application-default + # print-access-token + session.run(copy_credentials, application_default_credentials) + + # Calling this forces the Cloud SDK to read the credentials we just wrote + # and obtain a new access token with those credentials. This validates + # that our credentials matches the format expected by gcloud. + # Silent is set to True to prevent leaking secrets in test logs. + session.run( + GCLOUD, "auth", "application-default", "print-access-token", silent=True + ) + + +# Test sesssions + +TEST_DEPENDENCIES_ASYNC = ["aiohttp", "pytest-asyncio", "nest-asyncio"] +TEST_DEPENDENCIES_SYNC = ["pytest", "requests", "mock"] +PYTHON_VERSIONS_ASYNC = ["3.7"] +PYTHON_VERSIONS_SYNC = ["2.7", "3.7"] + + +def default(session, *test_paths): + # replace 'session._runner.friendly_name' with + # session.name once nox has released a new version + # https://github.com/theacodes/nox/pull/386 + sponge_log = f"--junitxml=system_{str(session._runner.friendly_name)}_sponge_log.xml" + session.run( + "pytest", sponge_log, *test_paths, + ) + + +@nox.session(python=PYTHON_VERSIONS_SYNC) +def service_account_sync(session): + session.install(*TEST_DEPENDENCIES_SYNC) + session.install(LIBRARY_DIR) + default( + session, + "system_tests_sync/test_service_account.py", + *session.posargs, + ) + + +@nox.session(python=PYTHON_VERSIONS_SYNC) +def default_explicit_service_account(session): + session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE + session.env[EXPECT_PROJECT_ENV] = "1" + session.install(*TEST_DEPENDENCIES_SYNC) + session.install(LIBRARY_DIR) + default( + session, + "system_tests_sync/test_default.py", + "system_tests_sync/test_id_token.py", + *session.posargs, + ) + + +@nox.session(python=PYTHON_VERSIONS_SYNC) +def default_explicit_authorized_user(session): + session.env[EXPLICIT_CREDENTIALS_ENV] = AUTHORIZED_USER_FILE + session.install(*TEST_DEPENDENCIES_SYNC) + session.install(LIBRARY_DIR) + default( + session, + "system_tests_sync/test_default.py", + *session.posargs, + ) + + +@nox.session(python=PYTHON_VERSIONS_SYNC) +def default_explicit_authorized_user_explicit_project(session): + session.env[EXPLICIT_CREDENTIALS_ENV] = AUTHORIZED_USER_FILE + session.env[EXPLICIT_PROJECT_ENV] = "example-project" + session.env[EXPECT_PROJECT_ENV] = "1" + session.install(*TEST_DEPENDENCIES_SYNC) + session.install(LIBRARY_DIR) + default( + session, + "system_tests_sync/test_default.py", + *session.posargs, + ) + + +@nox.session(python=PYTHON_VERSIONS_SYNC) +def default_cloud_sdk_service_account(session): + configure_cloud_sdk(session, SERVICE_ACCOUNT_FILE) + session.env[EXPECT_PROJECT_ENV] = "1" + session.install(*TEST_DEPENDENCIES_SYNC) + session.install(LIBRARY_DIR) + default( + session, + "system_tests_sync/test_default.py", + *session.posargs, + ) + + +@nox.session(python=PYTHON_VERSIONS_SYNC) +def default_cloud_sdk_authorized_user(session): + configure_cloud_sdk(session, AUTHORIZED_USER_FILE) + session.install(*TEST_DEPENDENCIES_SYNC) + session.install(LIBRARY_DIR) + default( + session, + "system_tests_sync/test_default.py", + *session.posargs, + ) + + +@nox.session(python=PYTHON_VERSIONS_SYNC) +def default_cloud_sdk_authorized_user_configured_project(session): + configure_cloud_sdk(session, AUTHORIZED_USER_FILE, project=True) + session.env[EXPECT_PROJECT_ENV] = "1" + session.install(*TEST_DEPENDENCIES_SYNC) + session.install(LIBRARY_DIR) + default( + session, + "system_tests_sync/test_default.py", + *session.posargs, + ) + + +@nox.session(python=PYTHON_VERSIONS_SYNC) +def compute_engine(session): + session.install(*TEST_DEPENDENCIES_SYNC) + # unset Application Default Credentials so + # credentials are detected from environment + del session.virtualenv.env["GOOGLE_APPLICATION_CREDENTIALS"] + session.install(LIBRARY_DIR) + default( + session, + "system_tests_sync/test_compute_engine.py", + *session.posargs, + ) + + +@nox.session(python=["2.7"]) +def app_engine(session): + if SKIP_GAE_TEST_ENV in os.environ: + session.log("Skipping App Engine tests.") + return + + session.install(LIBRARY_DIR) + # Unlike the default tests above, the App Engine system test require a + # 'real' gcloud sdk installation that is configured to deploy to an + # app engine project. + # Grab the project ID from the cloud sdk. + project_id = ( + subprocess.check_output( + ["gcloud", "config", "list", "project", "--format", "value(core.project)"] + ) + .decode("utf-8") + .strip() + ) + + if not project_id: + session.error( + "The Cloud SDK must be installed and configured to deploy to App " "Engine." + ) + + application_url = GAE_APP_URL_TMPL.format(GAE_TEST_APP_SERVICE, project_id) + + # Vendor in the test application's dependencies + session.chdir(os.path.join(HERE, "system_tests_sync/app_engine_test_app")) + session.install(*TEST_DEPENDENCIES_SYNC) + session.run( + "pip", "install", "--target", "lib", "-r", "requirements.txt", silent=True + ) + + # Deploy the application. + session.run("gcloud", "app", "deploy", "-q", "app.yaml") + + # Run the tests + session.env["TEST_APP_URL"] = application_url + session.chdir(HERE) + default( + session, "system_tests_sync/test_app_engine.py", + ) + + +@nox.session(python=PYTHON_VERSIONS_SYNC) +def grpc(session): + session.install(LIBRARY_DIR) + session.install(*TEST_DEPENDENCIES_SYNC, "google-cloud-pubsub==1.7.0") + session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE + default( + session, + "system_tests_sync/test_grpc.py", + *session.posargs, + ) + + +@nox.session(python=PYTHON_VERSIONS_SYNC) +def requests(session): + session.install(LIBRARY_DIR) + session.install(*TEST_DEPENDENCIES_SYNC) + session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE + default( + session, + "system_tests_sync/test_requests.py", + *session.posargs, + ) + + +@nox.session(python=PYTHON_VERSIONS_SYNC) +def urllib3(session): + session.install(LIBRARY_DIR) + session.install(*TEST_DEPENDENCIES_SYNC) + session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE + default( + session, + "system_tests_sync/test_urllib3.py", + *session.posargs, + ) + + +@nox.session(python=PYTHON_VERSIONS_SYNC) +def mtls_http(session): + session.install(LIBRARY_DIR) + session.install(*TEST_DEPENDENCIES_SYNC, "pyopenssl") + session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE + default( + session, + "system_tests_sync/test_mtls_http.py", + *session.posargs, + ) + + +@nox.session(python=PYTHON_VERSIONS_SYNC) +def external_accounts(session): + session.install( + *TEST_DEPENDENCIES_SYNC, + LIBRARY_DIR, + "google-api-python-client", + ) + default( + session, + "system_tests_sync/test_external_accounts.py", + *session.posargs, + ) + + +@nox.session(python=PYTHON_VERSIONS_SYNC) +def downscoping(session): + session.install( + *TEST_DEPENDENCIES_SYNC, + LIBRARY_DIR, + "google-cloud-storage", + ) + default( + session, + "system_tests_sync/test_downscoping.py", + *session.posargs, + ) + + +# ASYNC SYSTEM TESTS + +@nox.session(python=PYTHON_VERSIONS_ASYNC) +def service_account_async(session): + session.install(*(TEST_DEPENDENCIES_SYNC + TEST_DEPENDENCIES_ASYNC)) + session.install(LIBRARY_DIR) + default( + session, + "system_tests_async/test_service_account.py", + *session.posargs, + ) + + +@nox.session(python=PYTHON_VERSIONS_ASYNC) +def default_explicit_service_account_async(session): + session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE + session.env[EXPECT_PROJECT_ENV] = "1" + session.install(*(TEST_DEPENDENCIES_SYNC + TEST_DEPENDENCIES_ASYNC)) + session.install(LIBRARY_DIR) + default( + session, + "system_tests_async/test_default.py", + "system_tests_async/test_id_token.py", + *session.posargs, + ) + + +@nox.session(python=PYTHON_VERSIONS_ASYNC) +def default_explicit_authorized_user_async(session): + session.env[EXPLICIT_CREDENTIALS_ENV] = AUTHORIZED_USER_FILE + session.install(*(TEST_DEPENDENCIES_SYNC + TEST_DEPENDENCIES_ASYNC)) + session.install(LIBRARY_DIR) + default( + session, + "system_tests_async/test_default.py", + *session.posargs, + ) + + +@nox.session(python=PYTHON_VERSIONS_ASYNC) +def default_explicit_authorized_user_explicit_project_async(session): + session.env[EXPLICIT_CREDENTIALS_ENV] = AUTHORIZED_USER_FILE + session.env[EXPLICIT_PROJECT_ENV] = "example-project" + session.env[EXPECT_PROJECT_ENV] = "1" + session.install(*(TEST_DEPENDENCIES_SYNC + TEST_DEPENDENCIES_ASYNC)) + session.install(LIBRARY_DIR) + default( + session, + "system_tests_async/test_default.py", + *session.posargs, + ) + + +@nox.session(python=PYTHON_VERSIONS_ASYNC) +def default_cloud_sdk_service_account_async(session): + configure_cloud_sdk(session, SERVICE_ACCOUNT_FILE) + session.env[EXPECT_PROJECT_ENV] = "1" + session.install(*(TEST_DEPENDENCIES_SYNC + TEST_DEPENDENCIES_ASYNC)) + session.install(LIBRARY_DIR) + default( + session, + "system_tests_async/test_default.py", + *session.posargs, + ) + + +@nox.session(python=PYTHON_VERSIONS_ASYNC) +def default_cloud_sdk_authorized_user_async(session): + configure_cloud_sdk(session, AUTHORIZED_USER_FILE) + session.install(*(TEST_DEPENDENCIES_SYNC + TEST_DEPENDENCIES_ASYNC)) + session.install(LIBRARY_DIR) + default( + session, + "system_tests_async/test_default.py", + *session.posargs, + ) + + +@nox.session(python=PYTHON_VERSIONS_ASYNC) +def default_cloud_sdk_authorized_user_configured_project_async(session): + configure_cloud_sdk(session, AUTHORIZED_USER_FILE, project=True) + session.env[EXPECT_PROJECT_ENV] = "1" + session.install(*(TEST_DEPENDENCIES_SYNC + TEST_DEPENDENCIES_ASYNC)) + session.install(LIBRARY_DIR) + default( + session, + "system_tests_async/test_default.py", + *session.posargs, + ) |