1# Copyright 2015 gRPC authors. 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"""Provides distutils command classes for the gRPC Python setup process.""" 15 16from distutils import errors as _errors 17import glob 18import os 19import os.path 20import platform 21import re 22import shutil 23import subprocess 24import sys 25import traceback 26 27import setuptools 28from setuptools.command import build_ext 29from setuptools.command import build_py 30from setuptools.command import easy_install 31from setuptools.command import install 32from setuptools.command import test 33 34PYTHON_STEM = os.path.dirname(os.path.abspath(__file__)) 35GRPC_STEM = os.path.abspath(PYTHON_STEM + '../../../../') 36GRPC_PROTO_STEM = os.path.join(GRPC_STEM, 'src', 'proto') 37PROTO_STEM = os.path.join(PYTHON_STEM, 'src', 'proto') 38PYTHON_PROTO_TOP_LEVEL = os.path.join(PYTHON_STEM, 'src') 39 40 41class CommandError(object): 42 pass 43 44 45class GatherProto(setuptools.Command): 46 47 description = 'gather proto dependencies' 48 user_options = [] 49 50 def initialize_options(self): 51 pass 52 53 def finalize_options(self): 54 pass 55 56 def run(self): 57 # TODO(atash) ensure that we're running from the repository directory when 58 # this command is used 59 try: 60 shutil.rmtree(PROTO_STEM) 61 except Exception as error: 62 # We don't care if this command fails 63 pass 64 shutil.copytree(GRPC_PROTO_STEM, PROTO_STEM) 65 for root, _, _ in os.walk(PYTHON_PROTO_TOP_LEVEL): 66 path = os.path.join(root, '__init__.py') 67 open(path, 'a').close() 68 69 70class BuildPy(build_py.build_py): 71 """Custom project build command.""" 72 73 def run(self): 74 try: 75 self.run_command('build_package_protos') 76 except CommandError as error: 77 sys.stderr.write('warning: %s\n' % error.message) 78 build_py.build_py.run(self) 79 80 81class TestLite(setuptools.Command): 82 """Command to run tests without fetching or building anything.""" 83 84 description = 'run tests without fetching or building anything.' 85 user_options = [] 86 87 def initialize_options(self): 88 pass 89 90 def finalize_options(self): 91 # distutils requires this override. 92 pass 93 94 def run(self): 95 self._add_eggs_to_path() 96 97 import tests 98 loader = tests.Loader() 99 loader.loadTestsFromNames(['tests']) 100 runner = tests.Runner() 101 result = runner.run(loader.suite) 102 if not result.wasSuccessful(): 103 sys.exit('Test failure') 104 105 def _add_eggs_to_path(self): 106 """Fetch install and test requirements""" 107 self.distribution.fetch_build_eggs(self.distribution.install_requires) 108 self.distribution.fetch_build_eggs(self.distribution.tests_require) 109 110 111class TestGevent(setuptools.Command): 112 """Command to run tests w/gevent.""" 113 114 BANNED_TESTS = ( 115 # These tests send a lot of RPCs and are really slow on gevent. They will 116 # eventually succeed, but need to dig into performance issues. 117 'unit._cython._no_messages_server_completion_queue_per_call_test.Test.test_rpcs', 118 'unit._cython._no_messages_single_server_completion_queue_test.Test.test_rpcs', 119 # I have no idea why this doesn't work in gevent, but it shouldn't even be 120 # using the c-core 121 'testing._client_test.ClientTest.test_infinite_request_stream_real_time', 122 # TODO(https://github.com/grpc/grpc/issues/15743) enable this test 123 'unit._session_cache_test.SSLSessionCacheTest.testSSLSessionCacheLRU', 124 # TODO(https://github.com/grpc/grpc/issues/14789) enable this test 125 'unit._server_ssl_cert_config_test', 126 # TODO(https://github.com/grpc/grpc/issues/14901) enable this test 127 'protoc_plugin._python_plugin_test.PythonPluginTest', 128 # Beta API is unsupported for gevent 129 'protoc_plugin.beta_python_plugin_test', 130 'unit.beta._beta_features_test', 131 ) 132 description = 'run tests with gevent. Assumes grpc/gevent are installed' 133 user_options = [] 134 135 def initialize_options(self): 136 pass 137 138 def finalize_options(self): 139 # distutils requires this override. 140 pass 141 142 def run(self): 143 from gevent import monkey 144 monkey.patch_all() 145 146 import tests 147 148 import grpc.experimental.gevent 149 grpc.experimental.gevent.init_gevent() 150 151 import gevent 152 153 import tests 154 loader = tests.Loader() 155 loader.loadTestsFromNames(['tests']) 156 runner = tests.Runner() 157 runner.skip_tests(self.BANNED_TESTS) 158 result = gevent.spawn(runner.run, loader.suite) 159 result.join() 160 if not result.value.wasSuccessful(): 161 sys.exit('Test failure') 162 163 164class RunInterop(test.test): 165 166 description = 'run interop test client/server' 167 user_options = [('args=', 'a', 'pass-thru arguments for the client/server'), 168 ('client', 'c', 'flag indicating to run the client'), 169 ('server', 's', 'flag indicating to run the server')] 170 171 def initialize_options(self): 172 self.args = '' 173 self.client = False 174 self.server = False 175 176 def finalize_options(self): 177 if self.client and self.server: 178 raise _errors.DistutilsOptionError( 179 'you may only specify one of client or server') 180 181 def run(self): 182 if self.distribution.install_requires: 183 self.distribution.fetch_build_eggs( 184 self.distribution.install_requires) 185 if self.distribution.tests_require: 186 self.distribution.fetch_build_eggs(self.distribution.tests_require) 187 if self.client: 188 self.run_client() 189 elif self.server: 190 self.run_server() 191 192 def run_server(self): 193 # We import here to ensure that our setuptools parent has had a chance to 194 # edit the Python system path. 195 from tests.interop import server 196 sys.argv[1:] = self.args.split() 197 server.serve() 198 199 def run_client(self): 200 # We import here to ensure that our setuptools parent has had a chance to 201 # edit the Python system path. 202 from tests.interop import client 203 sys.argv[1:] = self.args.split() 204 client.test_interoperability() 205 206 207class RunFork(test.test): 208 209 description = 'run fork test client' 210 user_options = [('args=', 'a', 'pass-thru arguments for the client')] 211 212 def initialize_options(self): 213 self.args = '' 214 215 def finalize_options(self): 216 # distutils requires this override. 217 pass 218 219 def run(self): 220 if self.distribution.install_requires: 221 self.distribution.fetch_build_eggs( 222 self.distribution.install_requires) 223 if self.distribution.tests_require: 224 self.distribution.fetch_build_eggs(self.distribution.tests_require) 225 # We import here to ensure that our setuptools parent has had a chance to 226 # edit the Python system path. 227 from tests.fork import client 228 sys.argv[1:] = self.args.split() 229 client.test_fork() 230