#!/usr/bin/env python3 # Copyright 2020 The Pigweed Authors # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. """Tests the generated pw_rpc code.""" from pathlib import Path import os import subprocess import tempfile import unittest TEST_PROTO_FILE = b"""\ syntax = "proto3"; package pw.rpc.test; message TestRequest { float integer = 1; } message TestResponse { int32 value = 1; } message TestStreamResponse { bytes chunk = 1; } message Empty {} service TestService { rpc TestRpc(TestRequest) returns (TestResponse) {} rpc TestStreamRpc(Empty) returns (stream TestStreamResponse) {} } """ EXPECTED_NANOPB_CODE = """\ #pragma once #include #include #include #include #include "pw_rpc/internal/method.h" #include "pw_rpc/server_context.h" #include "pw_rpc/service.h" #include "test.pb.h" namespace pw::rpc::internal { template class ServiceMethodTraits; } // namespace pw::rpc::internal namespace pw::rpc::test { namespace generated { template class TestService : public ::pw::rpc::Service { public: using ServerContext = ::pw::rpc::ServerContext; template using ServerWriter = ::pw::rpc::ServerWriter; constexpr TestService() : ::pw::rpc::Service(kServiceId, kMethods) {} TestService(const TestService&) = delete; TestService& operator=(const TestService&) = delete; static constexpr const char* name() { return "TestService"; } // Used by ServiceMethodTraits to identify a base service. constexpr void _PwRpcInternalGeneratedBase() const {} private: // Hash of "pw.rpc.test.TestService". static constexpr uint32_t kServiceId = 0xcc0f6de0; static ::pw::Status Invoke_TestRpc( ::pw::rpc::internal::ServerCall& call, const pw_rpc_test_TestRequest& request, pw_rpc_test_TestResponse& response) { return static_cast(call.service()) .TestRpc(call.context(), request, response); } static void Invoke_TestStreamRpc( ::pw::rpc::internal::ServerCall& call, const pw_rpc_test_TestRequest& request, ServerWriter& writer) { static_cast(call.service()) .TestStreamRpc(call.context(), request, writer); } static constexpr std::array<::pw::rpc::internal::Method, 2> kMethods = { ::pw::rpc::internal::Method::Unary( 0xbc924054, // Hash of "TestRpc" pw_rpc_test_TestRequest_fields, pw_rpc_test_TestResponse_fields), ::pw::rpc::internal::Method::ServerStreaming( 0xd97a28fa, // Hash of "TestStreamRpc" pw_rpc_test_TestRequest_fields, pw_rpc_test_TestStreamResponse_fields), }; template static constexpr const ::pw::rpc::internal::Method* MethodFor() { if constexpr (std::is_same_v) { if constexpr (impl_method == &Implementation::TestRpc) { return &std::get<0>(kMethods); } } if constexpr (std::is_same_v) { if constexpr (impl_method == &Implementation::TestStreamRpc) { return &std::get<1>(kMethods); } } return nullptr; } template friend class ::pw::rpc::internal::ServiceMethodTraits; }; } // namespace generated } // namespace pw::rpc::test """ class TestNanopbCodegen(unittest.TestCase): """Test case for nanopb code generation.""" def setUp(self): self._output_dir = tempfile.TemporaryDirectory() def tearDown(self): self._output_dir.cleanup() def test_nanopb_codegen(self): root = Path(os.getenv('PW_ROOT')) proto_dir = root / 'pw_rpc' / 'pw_rpc_test_protos' proto_file = proto_dir / 'test.proto' venv_bin = 'Scripts' if os.name == 'nt' else 'bin' plugin = root / '.python3-env' / venv_bin / 'pw_rpc_codegen' command = ( 'protoc', f'-I{proto_dir}', proto_file, '--plugin', f'protoc-gen-custom={plugin}', '--custom_out', self._output_dir.name, ) subprocess.run(command) generated_files = os.listdir(self._output_dir.name) self.assertEqual(len(generated_files), 1) self.assertEqual(generated_files[0], 'test.rpc.pb.h') # Read the generated file, ignoring its preamble. generated_code = Path(self._output_dir.name, generated_files[0]).read_text() generated_code = generated_code[generated_code.index('#pragma'):] self.assertEqual(generated_code, EXPECTED_NANOPB_CODE)