• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2016 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
15import abc
16import contextlib
17import importlib
18import os
19from os import path
20import pkgutil
21import platform
22import shutil
23import sys
24import tempfile
25import unittest
26
27import six
28
29import grpc
30from grpc_tools import protoc
31from tests.unit import test_common
32
33_MESSAGES_IMPORT = b'import "messages.proto";'
34_SPLIT_NAMESPACE = b'package grpc_protoc_plugin.invocation_testing.split;'
35_COMMON_NAMESPACE = b'package grpc_protoc_plugin.invocation_testing;'
36
37_RELATIVE_PROTO_PATH = 'relative_proto_path'
38_RELATIVE_PYTHON_OUT = 'relative_python_out'
39
40
41@contextlib.contextmanager
42def _system_path(path_insertion):
43    old_system_path = sys.path[:]
44    sys.path = sys.path[0:1] + path_insertion + sys.path[1:]
45    yield
46    sys.path = old_system_path
47
48
49# NOTE(nathaniel): https://twitter.com/exoplaneteer/status/677259364256747520
50# Life lesson "just always default to idempotence" reinforced.
51def _create_directory_tree(root, path_components_sequence):
52    created = set()
53    for path_components in path_components_sequence:
54        thus_far = ''
55        for path_component in path_components:
56            relative_path = path.join(thus_far, path_component)
57            if relative_path not in created:
58                os.makedirs(path.join(root, relative_path))
59                created.add(relative_path)
60            thus_far = path.join(thus_far, path_component)
61
62
63def _massage_proto_content(proto_content, test_name_bytes,
64                           messages_proto_relative_file_name_bytes):
65    package_substitution = (b'package grpc_protoc_plugin.invocation_testing.' +
66                            test_name_bytes + b';')
67    common_namespace_substituted = proto_content.replace(
68        _COMMON_NAMESPACE, package_substitution)
69    split_namespace_substituted = common_namespace_substituted.replace(
70        _SPLIT_NAMESPACE, package_substitution)
71    message_import_replaced = split_namespace_substituted.replace(
72        _MESSAGES_IMPORT,
73        b'import "' + messages_proto_relative_file_name_bytes + b'";')
74    return message_import_replaced
75
76
77def _packagify(directory):
78    for subdirectory, _, _ in os.walk(directory):
79        init_file_name = path.join(subdirectory, '__init__.py')
80        with open(init_file_name, 'wb') as init_file:
81            init_file.write(b'')
82
83
84class _Servicer(object):
85
86    def __init__(self, response_class):
87        self._response_class = response_class
88
89    def Call(self, request, context):
90        return self._response_class()
91
92
93def _protoc(proto_path, python_out, grpc_python_out_flag, grpc_python_out,
94            absolute_proto_file_names):
95    args = [
96        '',
97        '--proto_path={}'.format(proto_path),
98    ]
99    if python_out is not None:
100        args.append('--python_out={}'.format(python_out))
101    if grpc_python_out is not None:
102        args.append('--grpc_python_out={}:{}'.format(grpc_python_out_flag,
103                                                     grpc_python_out))
104    args.extend(absolute_proto_file_names)
105    return protoc.main(args)
106
107
108class _Mid2016ProtocStyle(object):
109
110    def name(self):
111        return 'Mid2016ProtocStyle'
112
113    def grpc_in_pb2_expected(self):
114        return True
115
116    def protoc(self, proto_path, python_out, absolute_proto_file_names):
117        return (_protoc(proto_path, python_out, 'grpc_1_0', python_out,
118                        absolute_proto_file_names),)
119
120
121class _SingleProtocExecutionProtocStyle(object):
122
123    def name(self):
124        return 'SingleProtocExecutionProtocStyle'
125
126    def grpc_in_pb2_expected(self):
127        return False
128
129    def protoc(self, proto_path, python_out, absolute_proto_file_names):
130        return (_protoc(proto_path, python_out, 'grpc_2_0', python_out,
131                        absolute_proto_file_names),)
132
133
134class _ProtoBeforeGrpcProtocStyle(object):
135
136    def name(self):
137        return 'ProtoBeforeGrpcProtocStyle'
138
139    def grpc_in_pb2_expected(self):
140        return False
141
142    def protoc(self, proto_path, python_out, absolute_proto_file_names):
143        pb2_protoc_exit_code = _protoc(proto_path, python_out, None, None,
144                                       absolute_proto_file_names)
145        pb2_grpc_protoc_exit_code = _protoc(proto_path, None, 'grpc_2_0',
146                                            python_out,
147                                            absolute_proto_file_names)
148        return pb2_protoc_exit_code, pb2_grpc_protoc_exit_code
149
150
151class _GrpcBeforeProtoProtocStyle(object):
152
153    def name(self):
154        return 'GrpcBeforeProtoProtocStyle'
155
156    def grpc_in_pb2_expected(self):
157        return False
158
159    def protoc(self, proto_path, python_out, absolute_proto_file_names):
160        pb2_grpc_protoc_exit_code = _protoc(proto_path, None, 'grpc_2_0',
161                                            python_out,
162                                            absolute_proto_file_names)
163        pb2_protoc_exit_code = _protoc(proto_path, python_out, None, None,
164                                       absolute_proto_file_names)
165        return pb2_grpc_protoc_exit_code, pb2_protoc_exit_code
166
167
168_PROTOC_STYLES = (
169    _Mid2016ProtocStyle(),
170    _SingleProtocExecutionProtocStyle(),
171    _ProtoBeforeGrpcProtocStyle(),
172    _GrpcBeforeProtoProtocStyle(),
173)
174
175
176@unittest.skipIf(platform.python_implementation() == 'PyPy',
177                 'Skip test if run with PyPy!')
178class _Test(six.with_metaclass(abc.ABCMeta, unittest.TestCase)):
179
180    def setUp(self):
181        self._directory = tempfile.mkdtemp(suffix=self.NAME, dir='.')
182        self._proto_path = path.join(self._directory, _RELATIVE_PROTO_PATH)
183        self._python_out = path.join(self._directory, _RELATIVE_PYTHON_OUT)
184
185        os.makedirs(self._proto_path)
186        os.makedirs(self._python_out)
187
188        proto_directories_and_names = {
189            (
190                self.MESSAGES_PROTO_RELATIVE_DIRECTORY_NAMES,
191                self.MESSAGES_PROTO_FILE_NAME,
192            ),
193            (
194                self.SERVICES_PROTO_RELATIVE_DIRECTORY_NAMES,
195                self.SERVICES_PROTO_FILE_NAME,
196            ),
197        }
198        messages_proto_relative_file_name_forward_slashes = '/'.join(
199            self.MESSAGES_PROTO_RELATIVE_DIRECTORY_NAMES +
200            (self.MESSAGES_PROTO_FILE_NAME,))
201        _create_directory_tree(
202            self._proto_path,
203            (relative_proto_directory_names for relative_proto_directory_names,
204             _ in proto_directories_and_names))
205        self._absolute_proto_file_names = set()
206        for relative_directory_names, file_name in proto_directories_and_names:
207            absolute_proto_file_name = path.join(
208                self._proto_path, *relative_directory_names + (file_name,))
209            raw_proto_content = pkgutil.get_data(
210                'tests.protoc_plugin.protos.invocation_testing',
211                path.join(*relative_directory_names + (file_name,)))
212            massaged_proto_content = _massage_proto_content(
213                raw_proto_content, self.NAME.encode(),
214                messages_proto_relative_file_name_forward_slashes.encode())
215            with open(absolute_proto_file_name, 'wb') as proto_file:
216                proto_file.write(massaged_proto_content)
217            self._absolute_proto_file_names.add(absolute_proto_file_name)
218
219    def tearDown(self):
220        shutil.rmtree(self._directory)
221
222    def _protoc(self):
223        protoc_exit_codes = self.PROTOC_STYLE.protoc(
224            self._proto_path, self._python_out, self._absolute_proto_file_names)
225        for protoc_exit_code in protoc_exit_codes:
226            self.assertEqual(0, protoc_exit_code)
227
228        _packagify(self._python_out)
229
230        generated_modules = {}
231        expected_generated_full_module_names = {
232            self.EXPECTED_MESSAGES_PB2,
233            self.EXPECTED_SERVICES_PB2,
234            self.EXPECTED_SERVICES_PB2_GRPC,
235        }
236        with _system_path([self._python_out]):
237            for full_module_name in expected_generated_full_module_names:
238                module = importlib.import_module(full_module_name)
239                generated_modules[full_module_name] = module
240
241        self._messages_pb2 = generated_modules[self.EXPECTED_MESSAGES_PB2]
242        self._services_pb2 = generated_modules[self.EXPECTED_SERVICES_PB2]
243        self._services_pb2_grpc = generated_modules[
244            self.EXPECTED_SERVICES_PB2_GRPC]
245
246    def _services_modules(self):
247        if self.PROTOC_STYLE.grpc_in_pb2_expected():
248            return self._services_pb2, self._services_pb2_grpc
249        else:
250            return (self._services_pb2_grpc,)
251
252    def test_imported_attributes(self):
253        self._protoc()
254
255        self._messages_pb2.Request
256        self._messages_pb2.Response
257        self._services_pb2.DESCRIPTOR.services_by_name['TestService']
258        for services_module in self._services_modules():
259            services_module.TestServiceStub
260            services_module.TestServiceServicer
261            services_module.add_TestServiceServicer_to_server
262
263    def test_call(self):
264        self._protoc()
265
266        for services_module in self._services_modules():
267            server = test_common.test_server()
268            services_module.add_TestServiceServicer_to_server(
269                _Servicer(self._messages_pb2.Response), server)
270            port = server.add_insecure_port('[::]:0')
271            server.start()
272            channel = grpc.insecure_channel('localhost:{}'.format(port))
273            stub = services_module.TestServiceStub(channel)
274            response = stub.Call(self._messages_pb2.Request())
275            self.assertEqual(self._messages_pb2.Response(), response)
276            server.stop(None)
277
278
279def _create_test_case_class(split_proto, protoc_style):
280    attributes = {}
281
282    name = '{}{}'.format('SplitProto' if split_proto else 'SameProto',
283                         protoc_style.name())
284    attributes['NAME'] = name
285
286    if split_proto:
287        attributes['MESSAGES_PROTO_RELATIVE_DIRECTORY_NAMES'] = (
288            'split_messages',
289            'sub',
290        )
291        attributes['MESSAGES_PROTO_FILE_NAME'] = 'messages.proto'
292        attributes['SERVICES_PROTO_RELATIVE_DIRECTORY_NAMES'] = (
293            'split_services',)
294        attributes['SERVICES_PROTO_FILE_NAME'] = 'services.proto'
295        attributes['EXPECTED_MESSAGES_PB2'] = 'split_messages.sub.messages_pb2'
296        attributes['EXPECTED_SERVICES_PB2'] = 'split_services.services_pb2'
297        attributes['EXPECTED_SERVICES_PB2_GRPC'] = (
298            'split_services.services_pb2_grpc')
299    else:
300        attributes['MESSAGES_PROTO_RELATIVE_DIRECTORY_NAMES'] = ()
301        attributes['MESSAGES_PROTO_FILE_NAME'] = 'same.proto'
302        attributes['SERVICES_PROTO_RELATIVE_DIRECTORY_NAMES'] = ()
303        attributes['SERVICES_PROTO_FILE_NAME'] = 'same.proto'
304        attributes['EXPECTED_MESSAGES_PB2'] = 'same_pb2'
305        attributes['EXPECTED_SERVICES_PB2'] = 'same_pb2'
306        attributes['EXPECTED_SERVICES_PB2_GRPC'] = 'same_pb2_grpc'
307
308    attributes['PROTOC_STYLE'] = protoc_style
309
310    attributes['__module__'] = _Test.__module__
311
312    return type('{}Test'.format(name), (_Test,), attributes)
313
314
315def _create_test_case_classes():
316    for split_proto in (
317            False,
318            True,
319    ):
320        for protoc_style in _PROTOC_STYLES:
321            yield _create_test_case_class(split_proto, protoc_style)
322
323
324def load_tests(loader, tests, pattern):
325    tests = tuple(
326        loader.loadTestsFromTestCase(test_case_class)
327        for test_case_class in _create_test_case_classes())
328    return unittest.TestSuite(tests=tests)
329
330
331if __name__ == '__main__':
332    unittest.main(verbosity=2)
333