• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 sys
24
25import setuptools
26from setuptools.command import build_ext
27from setuptools.command import build_py
28from setuptools.command import easy_install
29from setuptools.command import install
30from setuptools.command import test
31
32PYTHON_STEM = os.path.dirname(os.path.abspath(__file__))
33GRPC_STEM = os.path.abspath(PYTHON_STEM + '../../../../')
34GRPC_PROTO_STEM = os.path.join(GRPC_STEM, 'src', 'proto')
35PROTO_STEM = os.path.join(PYTHON_STEM, 'src', 'proto')
36PYTHON_PROTO_TOP_LEVEL = os.path.join(PYTHON_STEM, 'src')
37
38
39class CommandError(object):
40    pass
41
42
43class GatherProto(setuptools.Command):
44
45    description = 'gather proto dependencies'
46    user_options = []
47
48    def initialize_options(self):
49        pass
50
51    def finalize_options(self):
52        pass
53
54    def run(self):
55        # TODO(atash) ensure that we're running from the repository directory when
56        # this command is used
57        try:
58            shutil.rmtree(PROTO_STEM)
59        except Exception as error:
60            # We don't care if this command fails
61            pass
62        shutil.copytree(GRPC_PROTO_STEM, PROTO_STEM)
63        for root, _, _ in os.walk(PYTHON_PROTO_TOP_LEVEL):
64            path = os.path.join(root, '__init__.py')
65            open(path, 'a').close()
66
67
68class BuildPy(build_py.build_py):
69    """Custom project build command."""
70
71    def run(self):
72        try:
73            self.run_command('build_package_protos')
74        except CommandError as error:
75            sys.stderr.write('warning: %s\n' % error.message)
76        build_py.build_py.run(self)
77
78
79class TestLite(setuptools.Command):
80    """Command to run tests without fetching or building anything."""
81
82    description = 'run tests without fetching or building anything.'
83    user_options = []
84
85    def initialize_options(self):
86        pass
87
88    def finalize_options(self):
89        # distutils requires this override.
90        pass
91
92    def run(self):
93        self._add_eggs_to_path()
94
95        import tests
96        loader = tests.Loader()
97        loader.loadTestsFromNames(['tests'])
98        runner = tests.Runner(dedicated_threads=True)
99        result = runner.run(loader.suite)
100        if not result.wasSuccessful():
101            sys.exit('Test failure')
102
103    def _add_eggs_to_path(self):
104        """Fetch install and test requirements"""
105        self.distribution.fetch_build_eggs(self.distribution.install_requires)
106        self.distribution.fetch_build_eggs(self.distribution.tests_require)
107
108
109class TestPy3Only(setuptools.Command):
110    """Command to run tests for Python 3+ features.
111
112    This does not include asyncio tests, which are housed in a separate
113    directory.
114    """
115
116    description = 'run tests for py3+ features'
117    user_options = []
118
119    def initialize_options(self):
120        pass
121
122    def finalize_options(self):
123        pass
124
125    def run(self):
126        self._add_eggs_to_path()
127        import tests
128        loader = tests.Loader()
129        loader.loadTestsFromNames(['tests_py3_only'])
130        runner = tests.Runner()
131        result = runner.run(loader.suite)
132        if not result.wasSuccessful():
133            sys.exit('Test failure')
134
135    def _add_eggs_to_path(self):
136        self.distribution.fetch_build_eggs(self.distribution.install_requires)
137        self.distribution.fetch_build_eggs(self.distribution.tests_require)
138
139
140class TestAio(setuptools.Command):
141    """Command to run aio tests without fetching or building anything."""
142
143    description = 'run aio tests without fetching or building anything.'
144    user_options = []
145
146    def initialize_options(self):
147        pass
148
149    def finalize_options(self):
150        pass
151
152    def run(self):
153        self._add_eggs_to_path()
154
155        import tests
156        loader = tests.Loader()
157        loader.loadTestsFromNames(['tests_aio'])
158        # Even without dedicated threads, the framework will somehow spawn a
159        # new thread for tests to run upon. New thread doesn't have event loop
160        # attached by default, so initialization is needed.
161        runner = tests.Runner(dedicated_threads=False)
162        result = runner.run(loader.suite)
163        if not result.wasSuccessful():
164            sys.exit('Test failure')
165
166    def _add_eggs_to_path(self):
167        """Fetch install and test requirements"""
168        self.distribution.fetch_build_eggs(self.distribution.install_requires)
169        self.distribution.fetch_build_eggs(self.distribution.tests_require)
170
171
172class TestGevent(setuptools.Command):
173    """Command to run tests w/gevent."""
174
175    BANNED_TESTS = (
176        # Fork support is not compatible with gevent
177        'fork._fork_interop_test.ForkInteropTest',
178        # These tests send a lot of RPCs and are really slow on gevent.  They will
179        # eventually succeed, but need to dig into performance issues.
180        'unit._cython._no_messages_server_completion_queue_per_call_test.Test.test_rpcs',
181        'unit._cython._no_messages_single_server_completion_queue_test.Test.test_rpcs',
182        'unit._compression_test',
183        # TODO(https://github.com/grpc/grpc/issues/16890) enable this test
184        'unit._cython._channel_test.ChannelTest.test_multiple_channels_lonely_connectivity',
185        # I have no idea why this doesn't work in gevent, but it shouldn't even be
186        # using the c-core
187        'testing._client_test.ClientTest.test_infinite_request_stream_real_time',
188        # TODO(https://github.com/grpc/grpc/issues/15743) enable this test
189        'unit._session_cache_test.SSLSessionCacheTest.testSSLSessionCacheLRU',
190        # TODO(https://github.com/grpc/grpc/issues/14789) enable this test
191        'unit._server_ssl_cert_config_test',
192        # TODO(https://github.com/grpc/grpc/issues/14901) enable this test
193        'protoc_plugin._python_plugin_test.PythonPluginTest',
194        'protoc_plugin._python_plugin_test.SimpleStubsPluginTest',
195        # Beta API is unsupported for gevent
196        'protoc_plugin.beta_python_plugin_test',
197        'unit.beta._beta_features_test',
198        # TODO(https://github.com/grpc/grpc/issues/15411) unpin gevent version
199        # This test will stuck while running higher version of gevent
200        'unit._auth_context_test.AuthContextTest.testSessionResumption',
201        # TODO(https://github.com/grpc/grpc/issues/15411) enable these tests
202        'unit._channel_ready_future_test.ChannelReadyFutureTest.test_immediately_connectable_channel_connectivity',
203        "unit._cython._channel_test.ChannelTest.test_single_channel_lonely_connectivity",
204        'unit._exit_test.ExitTest.test_in_flight_unary_unary_call',
205        'unit._exit_test.ExitTest.test_in_flight_unary_stream_call',
206        'unit._exit_test.ExitTest.test_in_flight_stream_unary_call',
207        'unit._exit_test.ExitTest.test_in_flight_stream_stream_call',
208        'unit._exit_test.ExitTest.test_in_flight_partial_unary_stream_call',
209        'unit._exit_test.ExitTest.test_in_flight_partial_stream_unary_call',
210        'unit._exit_test.ExitTest.test_in_flight_partial_stream_stream_call',
211        # TODO(https://github.com/grpc/grpc/issues/18980): Reenable.
212        'unit._signal_handling_test.SignalHandlingTest',
213        'unit._metadata_flags_test',
214        'health_check._health_servicer_test.HealthServicerTest.test_cancelled_watch_removed_from_watch_list',
215        # TODO(https://github.com/grpc/grpc/issues/17330) enable these three tests
216        'channelz._channelz_servicer_test.ChannelzServicerTest.test_many_subchannels',
217        'channelz._channelz_servicer_test.ChannelzServicerTest.test_many_subchannels_and_sockets',
218        'channelz._channelz_servicer_test.ChannelzServicerTest.test_streaming_rpc',
219        # TODO(https://github.com/grpc/grpc/issues/15411) enable this test
220        'unit._cython._channel_test.ChannelTest.test_negative_deadline_connectivity',
221        # TODO(https://github.com/grpc/grpc/issues/15411) enable this test
222        'unit._local_credentials_test.LocalCredentialsTest',
223        # TODO(https://github.com/grpc/grpc/issues/22020) LocalCredentials
224        # aren't supported with custom io managers.
225        'unit._contextvars_propagation_test',
226        'testing._time_test.StrictRealTimeTest',
227    )
228    BANNED_WINDOWS_TESTS = (
229        # TODO(https://github.com/grpc/grpc/pull/15411) enable this test
230        'unit._dns_resolver_test.DNSResolverTest.test_connect_loopback',
231        # TODO(https://github.com/grpc/grpc/pull/15411) enable this test
232        'unit._server_test.ServerTest.test_failed_port_binding_exception',
233    )
234    description = 'run tests with gevent.  Assumes grpc/gevent are installed'
235    user_options = []
236
237    def initialize_options(self):
238        pass
239
240    def finalize_options(self):
241        # distutils requires this override.
242        pass
243
244    def run(self):
245        from gevent import monkey
246        monkey.patch_all()
247
248        import tests
249
250        import grpc.experimental.gevent
251        grpc.experimental.gevent.init_gevent()
252
253        import gevent
254
255        import tests
256        loader = tests.Loader()
257        loader.loadTestsFromNames(['tests'])
258        runner = tests.Runner()
259        if sys.platform == 'win32':
260            runner.skip_tests(self.BANNED_TESTS + self.BANNED_WINDOWS_TESTS)
261        else:
262            runner.skip_tests(self.BANNED_TESTS)
263        result = gevent.spawn(runner.run, loader.suite)
264        result.join()
265        if not result.value.wasSuccessful():
266            sys.exit('Test failure')
267
268
269class RunInterop(test.test):
270
271    description = 'run interop test client/server'
272    user_options = [
273        ('args=', None, 'pass-thru arguments for the client/server'),
274        ('client', None, 'flag indicating to run the client'),
275        ('server', None, 'flag indicating to run the server'),
276        ('use-asyncio', None, 'flag indicating to run the asyncio stack')
277    ]
278
279    def initialize_options(self):
280        self.args = ''
281        self.client = False
282        self.server = False
283        self.use_asyncio = False
284
285    def finalize_options(self):
286        if self.client and self.server:
287            raise _errors.DistutilsOptionError(
288                'you may only specify one of client or server')
289
290    def run(self):
291        if self.distribution.install_requires:
292            self.distribution.fetch_build_eggs(
293                self.distribution.install_requires)
294        if self.distribution.tests_require:
295            self.distribution.fetch_build_eggs(self.distribution.tests_require)
296        if self.client:
297            self.run_client()
298        elif self.server:
299            self.run_server()
300
301    def run_server(self):
302        # We import here to ensure that our setuptools parent has had a chance to
303        # edit the Python system path.
304        if self.use_asyncio:
305            import asyncio
306            from tests_aio.interop import server
307            sys.argv[1:] = self.args.split()
308            asyncio.get_event_loop().run_until_complete(server.serve())
309        else:
310            from tests.interop import server
311            sys.argv[1:] = self.args.split()
312            server.serve()
313
314    def run_client(self):
315        # We import here to ensure that our setuptools parent has had a chance to
316        # edit the Python system path.
317        from tests.interop import client
318        sys.argv[1:] = self.args.split()
319        client.test_interoperability()
320
321
322class RunFork(test.test):
323
324    description = 'run fork test client'
325    user_options = [('args=', 'a', 'pass-thru arguments for the client')]
326
327    def initialize_options(self):
328        self.args = ''
329
330    def finalize_options(self):
331        # distutils requires this override.
332        pass
333
334    def run(self):
335        if self.distribution.install_requires:
336            self.distribution.fetch_build_eggs(
337                self.distribution.install_requires)
338        if self.distribution.tests_require:
339            self.distribution.fetch_build_eggs(self.distribution.tests_require)
340        # We import here to ensure that our setuptools parent has had a chance to
341        # edit the Python system path.
342        from tests.fork import client
343        sys.argv[1:] = self.args.split()
344        client.test_fork()
345