1# Copyright 2018 The 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"""Tests of grpc_status.""" 15 16# NOTE(lidiz) This module only exists in Bazel BUILD file, for more details 17# please refer to comments in the "bazel_namespace_package_hack" module. 18try: 19 from tests import bazel_namespace_package_hack 20 bazel_namespace_package_hack.sys_path_to_site_dir_hack() 21except ImportError: 22 pass 23 24import unittest 25 26import logging 27import traceback 28 29import grpc 30from grpc_status import rpc_status 31 32from tests.unit import test_common 33 34from google.protobuf import any_pb2 35from google.rpc import code_pb2, status_pb2, error_details_pb2 36 37_STATUS_OK = '/test/StatusOK' 38_STATUS_NOT_OK = '/test/StatusNotOk' 39_ERROR_DETAILS = '/test/ErrorDetails' 40_INCONSISTENT = '/test/Inconsistent' 41_INVALID_CODE = '/test/InvalidCode' 42 43_REQUEST = b'\x00\x00\x00' 44_RESPONSE = b'\x01\x01\x01' 45 46_GRPC_DETAILS_METADATA_KEY = 'grpc-status-details-bin' 47 48_STATUS_DETAILS = 'This is an error detail' 49_STATUS_DETAILS_ANOTHER = 'This is another error detail' 50 51 52def _ok_unary_unary(request, servicer_context): 53 return _RESPONSE 54 55 56def _not_ok_unary_unary(request, servicer_context): 57 servicer_context.abort(grpc.StatusCode.INTERNAL, _STATUS_DETAILS) 58 59 60def _error_details_unary_unary(request, servicer_context): 61 details = any_pb2.Any() 62 details.Pack( 63 error_details_pb2.DebugInfo(stack_entries=traceback.format_stack(), 64 detail='Intentionally invoked')) 65 rich_status = status_pb2.Status( 66 code=code_pb2.INTERNAL, 67 message=_STATUS_DETAILS, 68 details=[details], 69 ) 70 servicer_context.abort_with_status(rpc_status.to_status(rich_status)) 71 72 73def _inconsistent_unary_unary(request, servicer_context): 74 rich_status = status_pb2.Status( 75 code=code_pb2.INTERNAL, 76 message=_STATUS_DETAILS, 77 ) 78 servicer_context.set_code(grpc.StatusCode.NOT_FOUND) 79 servicer_context.set_details(_STATUS_DETAILS_ANOTHER) 80 # User put inconsistent status information in trailing metadata 81 servicer_context.set_trailing_metadata( 82 ((_GRPC_DETAILS_METADATA_KEY, rich_status.SerializeToString()),)) 83 84 85def _invalid_code_unary_unary(request, servicer_context): 86 rich_status = status_pb2.Status( 87 code=42, 88 message='Invalid code', 89 ) 90 servicer_context.abort_with_status(rpc_status.to_status(rich_status)) 91 92 93class _GenericHandler(grpc.GenericRpcHandler): 94 95 def service(self, handler_call_details): 96 if handler_call_details.method == _STATUS_OK: 97 return grpc.unary_unary_rpc_method_handler(_ok_unary_unary) 98 elif handler_call_details.method == _STATUS_NOT_OK: 99 return grpc.unary_unary_rpc_method_handler(_not_ok_unary_unary) 100 elif handler_call_details.method == _ERROR_DETAILS: 101 return grpc.unary_unary_rpc_method_handler( 102 _error_details_unary_unary) 103 elif handler_call_details.method == _INCONSISTENT: 104 return grpc.unary_unary_rpc_method_handler( 105 _inconsistent_unary_unary) 106 elif handler_call_details.method == _INVALID_CODE: 107 return grpc.unary_unary_rpc_method_handler( 108 _invalid_code_unary_unary) 109 else: 110 return None 111 112 113class StatusTest(unittest.TestCase): 114 115 def setUp(self): 116 self._server = test_common.test_server() 117 self._server.add_generic_rpc_handlers((_GenericHandler(),)) 118 port = self._server.add_insecure_port('[::]:0') 119 self._server.start() 120 121 self._channel = grpc.insecure_channel('localhost:%d' % port) 122 123 def tearDown(self): 124 self._server.stop(None) 125 self._channel.close() 126 127 def test_status_ok(self): 128 _, call = self._channel.unary_unary(_STATUS_OK).with_call(_REQUEST) 129 130 # Succeed RPC doesn't have status 131 status = rpc_status.from_call(call) 132 self.assertIs(status, None) 133 134 def test_status_not_ok(self): 135 with self.assertRaises(grpc.RpcError) as exception_context: 136 self._channel.unary_unary(_STATUS_NOT_OK).with_call(_REQUEST) 137 rpc_error = exception_context.exception 138 139 self.assertEqual(rpc_error.code(), grpc.StatusCode.INTERNAL) 140 # Failed RPC doesn't automatically generate status 141 status = rpc_status.from_call(rpc_error) 142 self.assertIs(status, None) 143 144 def test_error_details(self): 145 with self.assertRaises(grpc.RpcError) as exception_context: 146 self._channel.unary_unary(_ERROR_DETAILS).with_call(_REQUEST) 147 rpc_error = exception_context.exception 148 149 status = rpc_status.from_call(rpc_error) 150 self.assertEqual(rpc_error.code(), grpc.StatusCode.INTERNAL) 151 self.assertEqual(status.code, code_pb2.Code.Value('INTERNAL')) 152 153 # Check if the underlying proto message is intact 154 self.assertEqual( 155 status.details[0].Is(error_details_pb2.DebugInfo.DESCRIPTOR), True) 156 info = error_details_pb2.DebugInfo() 157 status.details[0].Unpack(info) 158 self.assertIn('_error_details_unary_unary', info.stack_entries[-1]) 159 160 def test_code_message_validation(self): 161 with self.assertRaises(grpc.RpcError) as exception_context: 162 self._channel.unary_unary(_INCONSISTENT).with_call(_REQUEST) 163 rpc_error = exception_context.exception 164 self.assertEqual(rpc_error.code(), grpc.StatusCode.NOT_FOUND) 165 166 # Code/Message validation failed 167 self.assertRaises(ValueError, rpc_status.from_call, rpc_error) 168 169 def test_invalid_code(self): 170 with self.assertRaises(grpc.RpcError) as exception_context: 171 self._channel.unary_unary(_INVALID_CODE).with_call(_REQUEST) 172 rpc_error = exception_context.exception 173 self.assertEqual(rpc_error.code(), grpc.StatusCode.UNKNOWN) 174 # Invalid status code exception raised during coversion 175 self.assertIn('Invalid status code', rpc_error.details()) 176 177 178if __name__ == '__main__': 179 logging.basicConfig() 180 unittest.main(verbosity=2) 181