1# Copyright 2019 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15from __future__ import print_function 16 17import os 18from pathlib import Path 19import sys 20from typing import Callable, Dict, List, Optional 21 22import nox 23 24 25# WARNING - WARNING - WARNING - WARNING - WARNING 26# WARNING - WARNING - WARNING - WARNING - WARNING 27# DO NOT EDIT THIS FILE EVER! 28# WARNING - WARNING - WARNING - WARNING - WARNING 29# WARNING - WARNING - WARNING - WARNING - WARNING 30 31BLACK_VERSION = "black==19.10b0" 32 33# Copy `noxfile_config.py` to your directory and modify it instead. 34 35# `TEST_CONFIG` dict is a configuration hook that allows users to 36# modify the test configurations. The values here should be in sync 37# with `noxfile_config.py`. Users will copy `noxfile_config.py` into 38# their directory and modify it. 39 40TEST_CONFIG = { 41 # You can opt out from the test for specific Python versions. 42 "ignored_versions": [], 43 # Old samples are opted out of enforcing Python type hints 44 # All new samples should feature them 45 "enforce_type_hints": False, 46 # An envvar key for determining the project id to use. Change it 47 # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a 48 # build specific Cloud project. You can also use your own string 49 # to use your own Cloud project. 50 "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", 51 # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', 52 # If you need to use a specific version of pip, 53 # change pip_version_override to the string representation 54 # of the version number, for example, "20.2.4" 55 "pip_version_override": None, 56 # A dictionary you want to inject into your test. Don't put any 57 # secrets here. These values will override predefined values. 58 "envs": {}, 59} 60 61 62try: 63 # Ensure we can import noxfile_config in the project's directory. 64 sys.path.append(".") 65 from noxfile_config import TEST_CONFIG_OVERRIDE 66except ImportError as e: 67 print("No user noxfile_config found: detail: {}".format(e)) 68 TEST_CONFIG_OVERRIDE = {} 69 70# Update the TEST_CONFIG with the user supplied values. 71TEST_CONFIG.update(TEST_CONFIG_OVERRIDE) 72 73 74def get_pytest_env_vars() -> Dict[str, str]: 75 """Returns a dict for pytest invocation.""" 76 ret = {} 77 78 # Override the GCLOUD_PROJECT and the alias. 79 env_key = TEST_CONFIG["gcloud_project_env"] 80 # This should error out if not set. 81 ret["GOOGLE_CLOUD_PROJECT"] = os.environ[env_key] 82 83 # Apply user supplied envs. 84 ret.update(TEST_CONFIG["envs"]) 85 return ret 86 87 88# DO NOT EDIT - automatically generated. 89# All versions used to test samples. 90ALL_VERSIONS = ["3.6", "3.7", "3.8", "3.9", "3.10"] 91 92# Any default versions that should be ignored. 93IGNORED_VERSIONS = TEST_CONFIG["ignored_versions"] 94 95TESTED_VERSIONS = sorted([v for v in ALL_VERSIONS if v not in IGNORED_VERSIONS]) 96 97INSTALL_LIBRARY_FROM_SOURCE = os.environ.get("INSTALL_LIBRARY_FROM_SOURCE", False) in ( 98 "True", 99 "true", 100) 101 102# Error if a python version is missing 103nox.options.error_on_missing_interpreters = True 104 105# 106# Style Checks 107# 108 109 110def _determine_local_import_names(start_dir: str) -> List[str]: 111 """Determines all import names that should be considered "local". 112 113 This is used when running the linter to insure that import order is 114 properly checked. 115 """ 116 file_ext_pairs = [os.path.splitext(path) for path in os.listdir(start_dir)] 117 return [ 118 basename 119 for basename, extension in file_ext_pairs 120 if extension == ".py" 121 or os.path.isdir(os.path.join(start_dir, basename)) 122 and basename not in ("__pycache__") 123 ] 124 125 126# Linting with flake8. 127# 128# We ignore the following rules: 129# E203: whitespace before ‘:’ 130# E266: too many leading ‘#’ for block comment 131# E501: line too long 132# I202: Additional newline in a section of imports 133# 134# We also need to specify the rules which are ignored by default: 135# ['E226', 'W504', 'E126', 'E123', 'W503', 'E24', 'E704', 'E121'] 136FLAKE8_COMMON_ARGS = [ 137 "--show-source", 138 "--builtin=gettext", 139 "--max-complexity=20", 140 "--import-order-style=google", 141 "--exclude=.nox,.cache,env,lib,generated_pb2,*_pb2.py,*_pb2_grpc.py", 142 "--ignore=E121,E123,E126,E203,E226,E24,E266,E501,E704,W503,W504,I202", 143 "--max-line-length=88", 144] 145 146 147@nox.session 148def lint(session: nox.sessions.Session) -> None: 149 if not TEST_CONFIG["enforce_type_hints"]: 150 session.install("flake8", "flake8-import-order") 151 else: 152 session.install("flake8", "flake8-import-order", "flake8-annotations") 153 154 local_names = _determine_local_import_names(".") 155 args = FLAKE8_COMMON_ARGS + [ 156 "--application-import-names", 157 ",".join(local_names), 158 ".", 159 ] 160 session.run("flake8", *args) 161 162 163# 164# Black 165# 166 167 168@nox.session 169def blacken(session: nox.sessions.Session) -> None: 170 session.install(BLACK_VERSION) 171 python_files = [path for path in os.listdir(".") if path.endswith(".py")] 172 173 session.run("black", *python_files) 174 175 176# 177# Sample Tests 178# 179 180 181PYTEST_COMMON_ARGS = ["--junitxml=sponge_log.xml"] 182 183 184def _session_tests( 185 session: nox.sessions.Session, post_install: Callable = None 186) -> None: 187 if TEST_CONFIG["pip_version_override"]: 188 pip_version = TEST_CONFIG["pip_version_override"] 189 session.install(f"pip=={pip_version}") 190 """Runs py.test for a particular project.""" 191 if os.path.exists("requirements.txt"): 192 if os.path.exists("constraints.txt"): 193 session.install("-r", "requirements.txt", "-c", "constraints.txt") 194 else: 195 session.install("-r", "requirements.txt") 196 197 if os.path.exists("requirements-test.txt"): 198 if os.path.exists("constraints-test.txt"): 199 session.install("-r", "requirements-test.txt", "-c", "constraints-test.txt") 200 else: 201 session.install("-r", "requirements-test.txt") 202 203 if INSTALL_LIBRARY_FROM_SOURCE: 204 session.install("-e", _get_repo_root()) 205 206 if post_install: 207 post_install(session) 208 209 session.run( 210 "pytest", 211 *(PYTEST_COMMON_ARGS + session.posargs), 212 # Pytest will return 5 when no tests are collected. This can happen 213 # on travis where slow and flaky tests are excluded. 214 # See http://doc.pytest.org/en/latest/_modules/_pytest/main.html 215 success_codes=[0, 5], 216 env=get_pytest_env_vars(), 217 ) 218 219 220@nox.session(python=ALL_VERSIONS) 221def py(session: nox.sessions.Session) -> None: 222 """Runs py.test for a sample using the specified version of Python.""" 223 if session.python in TESTED_VERSIONS: 224 _session_tests(session) 225 else: 226 session.skip( 227 "SKIPPED: {} tests are disabled for this sample.".format(session.python) 228 ) 229 230 231# 232# Readmegen 233# 234 235 236def _get_repo_root() -> Optional[str]: 237 """ Returns the root folder of the project. """ 238 # Get root of this repository. Assume we don't have directories nested deeper than 10 items. 239 p = Path(os.getcwd()) 240 for i in range(10): 241 if p is None: 242 break 243 if Path(p / ".git").exists(): 244 return str(p) 245 # .git is not available in repos cloned via Cloud Build 246 # setup.py is always in the library's root, so use that instead 247 # https://github.com/googleapis/synthtool/issues/792 248 if Path(p / "setup.py").exists(): 249 return str(p) 250 p = p.parent 251 raise Exception("Unable to detect repository root.") 252 253 254GENERATED_READMES = sorted([x for x in Path(".").rglob("*.rst.in")]) 255 256 257@nox.session 258@nox.parametrize("path", GENERATED_READMES) 259def readmegen(session: nox.sessions.Session, path: str) -> None: 260 """(Re-)generates the readme for a sample.""" 261 session.install("jinja2", "pyyaml") 262 dir_ = os.path.dirname(path) 263 264 if os.path.exists(os.path.join(dir_, "requirements.txt")): 265 session.install("-r", os.path.join(dir_, "requirements.txt")) 266 267 in_file = os.path.join(dir_, "README.rst.in") 268 session.run( 269 "python", _get_repo_root() + "/scripts/readme-gen/readme_gen.py", in_file 270 ) 271