xref: /aosp_15_r20/external/grpc-grpc/src/python/grpcio_tests/commands.py (revision cc02d7e222339f7a4f6ba5f422e6413f4bd931f2)
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 setuptools command classes for the gRPC Python setup process."""
15
16import glob
17import os
18import os.path
19import platform
20import re
21import shutil
22import sys
23
24import setuptools
25from setuptools import errors as _errors
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    description = "gather proto dependencies"
45    user_options = []
46
47    def initialize_options(self):
48        pass
49
50    def finalize_options(self):
51        pass
52
53    def run(self):
54        # TODO(atash) ensure that we're running from the repository directory when
55        # this command is used
56        try:
57            shutil.rmtree(PROTO_STEM)
58        except Exception as error:
59            # We don't care if this command fails
60            pass
61        shutil.copytree(GRPC_PROTO_STEM, PROTO_STEM)
62        for root, _, _ in os.walk(PYTHON_PROTO_TOP_LEVEL):
63            path = os.path.join(root, "__init__.py")
64            open(path, "a").close()
65
66
67class BuildPy(build_py.build_py):
68    """Custom project build command."""
69
70    def run(self):
71        try:
72            self.run_command("build_package_protos")
73        except CommandError as error:
74            sys.stderr.write("warning: %s\n" % error.message)
75        build_py.build_py.run(self)
76
77
78class TestLite(setuptools.Command):
79    """Command to run tests without fetching or building anything."""
80
81    description = "run tests without fetching or building anything."
82    user_options = []
83
84    def initialize_options(self):
85        pass
86
87    def finalize_options(self):
88        # distutils requires this override.
89        pass
90
91    def run(self):
92        import tests
93
94        loader = tests.Loader()
95        loader.loadTestsFromNames(["tests"])
96        runner = tests.Runner(dedicated_threads=True)
97        result = runner.run(loader.suite)
98        if not result.wasSuccessful():
99            sys.exit("Test failure")
100
101
102class TestPy3Only(setuptools.Command):
103    """Command to run tests for Python 3+ features.
104
105    This does not include asyncio tests, which are housed in a separate
106    directory.
107    """
108
109    description = "run tests for py3+ features"
110    user_options = []
111
112    def initialize_options(self):
113        pass
114
115    def finalize_options(self):
116        pass
117
118    def run(self):
119        import tests
120
121        loader = tests.Loader()
122        loader.loadTestsFromNames(["tests_py3_only"])
123        runner = tests.Runner()
124        result = runner.run(loader.suite)
125        if not result.wasSuccessful():
126            sys.exit("Test failure")
127
128
129class TestAio(setuptools.Command):
130    """Command to run aio tests without fetching or building anything."""
131
132    description = "run aio tests without fetching or building anything."
133    user_options = []
134
135    def initialize_options(self):
136        pass
137
138    def finalize_options(self):
139        pass
140
141    def run(self):
142        import tests
143
144        loader = tests.Loader()
145        loader.loadTestsFromNames(["tests_aio"])
146        # Even without dedicated threads, the framework will somehow spawn a
147        # new thread for tests to run upon. New thread doesn't have event loop
148        # attached by default, so initialization is needed.
149        runner = tests.Runner(dedicated_threads=False)
150        result = runner.run(loader.suite)
151        if not result.wasSuccessful():
152            sys.exit("Test failure")
153
154
155class RunInterop(test.test):
156    description = "run interop test client/server"
157    user_options = [
158        ("args=", None, "pass-thru arguments for the client/server"),
159        ("client", None, "flag indicating to run the client"),
160        ("server", None, "flag indicating to run the server"),
161        ("use-asyncio", None, "flag indicating to run the asyncio stack"),
162    ]
163
164    def initialize_options(self):
165        self.args = ""
166        self.client = False
167        self.server = False
168        self.use_asyncio = False
169
170    def finalize_options(self):
171        if self.client and self.server:
172            raise _errors.OptionError(
173                "you may only specify one of client or server"
174            )
175
176    def run(self):
177        if self.client:
178            self.run_client()
179        elif self.server:
180            self.run_server()
181
182    def run_server(self):
183        # We import here to ensure that our setuptools parent has had a chance to
184        # edit the Python system path.
185        if self.use_asyncio:
186            import asyncio
187
188            from tests_aio.interop import server
189
190            sys.argv[1:] = self.args.split()
191            args = server.parse_interop_server_arguments(sys.argv)
192            asyncio.get_event_loop().run_until_complete(server.serve(args))
193        else:
194            from tests.interop import server
195
196            sys.argv[1:] = self.args.split()
197            server.serve(server.parse_interop_server_arguments(sys.argv))
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
204        sys.argv[1:] = self.args.split()
205        client.test_interoperability(client.parse_interop_client_args(sys.argv))
206
207
208class RunFork(test.test):
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        # We import here to ensure that our setuptools parent has had a chance to
221        # edit the Python system path.
222        from tests.fork import client
223
224        sys.argv[1:] = self.args.split()
225        client.test_fork()
226