2 # Copyright 2020 The Pigweed Authors
4 # Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 # use this file except in compliance with the License. You may obtain a copy of
8 # https://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 # License for the specific language governing permissions and limitations under
15 """Tests compiling and importing Python protos on the fly."""
17 from pathlib import Path
21 from pw_protobuf_compiler import python_protos
26 package pw.protobuf_compiler.test1;
29 uint32 magic_number = 1;
32 message AnotherMessage {
36 I_DONT_WANT_TO_TALK_ABOUT_IT = 2;
43 service PublicService {
44 rpc Unary(SomeMessage) returns (AnotherMessage) {}
45 rpc ServerStreaming(SomeMessage) returns (stream AnotherMessage) {}
46 rpc ClientStreaming(stream SomeMessage) returns (AnotherMessage) {}
47 rpc BidiStreaming(stream SomeMessage) returns (stream AnotherMessage) {}
54 package pw.protobuf_compiler.test2;
57 optional float magic_number = 1;
64 rpc Unary(Request) returns (Response) {}
68 rpc BidiStreaming(stream Request) returns (stream Response) {}
75 package pw.protobuf_compiler.test2;
83 repeated int64 value = 1;
89 class TestCompileAndImport(unittest.TestCase):
91 self._proto_dir = tempfile.TemporaryDirectory(prefix='proto_test')
94 for i, contents in enumerate([PROTO_1, PROTO_2, PROTO_3], 1):
95 self._protos.append(Path(self._proto_dir.name, f'test_{i}.proto'))
96 self._protos[-1].write_text(contents)
99 self._proto_dir.cleanup()
101 def test_compile_to_temp_dir_and_import(self):
104 for m in python_protos.compile_and_import(self._protos)
106 self.assertEqual(3, len(modules))
108 # Make sure the protobuf modules contain what we expect them to.
109 mod = modules['test_1.proto']
111 4, len(mod.DESCRIPTOR.services_by_name['PublicService'].methods))
113 mod = modules['test_2.proto']
114 self.assertEqual(mod.Request(magic_number=1.5).magic_number, 1.5)
115 self.assertEqual(2, len(mod.DESCRIPTOR.services_by_name))
117 mod = modules['test_3.proto']
118 self.assertEqual(mod.Hello(value=[123, 456]).value, [123, 456])
121 class TestProtoLibrary(TestCompileAndImport):
122 """Tests the Library class."""
125 self._library = python_protos.Library(
126 python_protos.compile_and_import(self._protos))
128 def test_packages_can_access_messages(self):
129 msg = self._library.packages.pw.protobuf_compiler.test1.SomeMessage
130 self.assertEqual(msg(magic_number=123).magic_number, 123)
132 def test_packages_finds_across_modules(self):
133 msg = self._library.packages.pw.protobuf_compiler.test2.Request
134 self.assertEqual(msg(magic_number=50).magic_number, 50)
136 val = self._library.packages.pw.protobuf_compiler.test2.YO
137 self.assertEqual(val, 0)
139 def test_packages_invalid_name(self):
140 with self.assertRaises(AttributeError):
141 _ = self._library.packages.nothing
143 with self.assertRaises(AttributeError):
144 _ = self._library.packages.pw.NOT_HERE
146 with self.assertRaises(AttributeError):
147 _ = self._library.packages.pw.protobuf_compiler.test1.NotARealMsg
149 def test_access_modules_by_package(self):
150 test1 = self._library.modules_by_package['pw.protobuf_compiler.test1']
151 self.assertEqual(len(test1), 1)
152 self.assertEqual(test1[0].AnotherMessage.Result.Value('FAILED'), 0)
154 test2 = self._library.modules_by_package['pw.protobuf_compiler.test2']
155 self.assertEqual(len(test2), 2)
157 def test_access_modules_by_package_unknown(self):
158 with self.assertRaises(KeyError):
159 _ = self._library.modules_by_package['pw.not_real']
161 def test_library_from_strings(self):
162 # Replace the package to avoid conflicts with the other proto imports
164 p.replace('pw.protobuf_compiler', 'proto.library.test')
165 for p in [PROTO_1, PROTO_2, PROTO_3]
168 library = python_protos.Library.from_strings(new_protos)
170 # Make sure we can safely import the same proto contents multiple times.
171 library = python_protos.Library.from_strings(new_protos)
173 msg = library.packages.proto.library.test.test2.Request
174 self.assertEqual(msg(magic_number=50).magic_number, 50)
176 val = library.packages.proto.library.test.test2.YO
177 self.assertEqual(val, 0)
179 def test_access_nested_packages_by_name(self):
180 self.assertIs(self._library.packages['pw.protobuf_compiler.test1'],
181 self._library.packages.pw.protobuf_compiler.test1)
182 self.assertIs(self._library.packages.pw['protobuf_compiler.test1'],
183 self._library.packages.pw.protobuf_compiler.test1)
184 self.assertIs(self._library.packages.pw.protobuf_compiler['test1'],
185 self._library.packages.pw.protobuf_compiler.test1)
187 def test_access_nested_packages_by_name_unknown_package(self):
188 with self.assertRaises(KeyError):
189 _ = self._library.packages['']
191 with self.assertRaises(KeyError):
192 _ = self._library.packages['.']
194 with self.assertRaises(KeyError):
195 _ = self._library.packages['protobuf_compiler.test1']
197 with self.assertRaises(KeyError):
198 _ = self._library.packages.pw['pw.protobuf_compiler.test1']
200 with self.assertRaises(KeyError):
201 _ = self._library.packages.pw.protobuf_compiler['not here']
204 if __name__ == '__main__':