diff --git a/src/proto/grpc/testing/echo.proto b/src/proto/grpc/testing/echo.proto index db583a1305b..c4233035dbc 100644 --- a/src/proto/grpc/testing/echo.proto +++ b/src/proto/grpc/testing/echo.proto @@ -15,15 +15,17 @@ syntax = "proto3"; +package grpc.testing; + import "src/proto/grpc/testing/echo_messages.proto"; import "src/proto/grpc/testing/simple_messages.proto"; -package grpc.testing; - service EchoTestService { rpc Echo(EchoRequest) returns (EchoResponse); rpc Echo1(EchoRequest) returns (EchoResponse); rpc Echo2(EchoRequest) returns (EchoResponse); + rpc CheckDeadlineUpperBound(SimpleRequest) returns (StringValue); + rpc CheckDeadlineSet(SimpleRequest) returns (StringValue); // A service which checks that the initial metadata sent over contains some // expected key value pair rpc CheckClientInitialMetadata(SimpleRequest) returns (SimpleResponse); @@ -64,5 +66,4 @@ service UnimplementedEchoService { } // A service without any rpc defined to test coverage. -service NoRpcService { -} +service NoRpcService {} diff --git a/src/proto/grpc/testing/simple_messages.proto b/src/proto/grpc/testing/simple_messages.proto index 4b65d18909d..3afe236b459 100644 --- a/src/proto/grpc/testing/simple_messages.proto +++ b/src/proto/grpc/testing/simple_messages.proto @@ -17,8 +17,10 @@ syntax = "proto3"; package grpc.testing; -message SimpleRequest { -} +message SimpleRequest {} + +message SimpleResponse {} -message SimpleResponse { +message StringValue { + string message = 1; } diff --git a/test/core/util/test_config.cc b/test/core/util/test_config.cc index 3b5e77c86d7..6dc43e725b3 100644 --- a/test/core/util/test_config.cc +++ b/test/core/util/test_config.cc @@ -368,6 +368,14 @@ gpr_timespec grpc_timeout_milliseconds_to_deadline(int64_t time_ms) { GPR_TIMESPAN)); } +gpr_timespec grpc_timeout_microseconds_to_deadline(int64_t time_micros) { + return gpr_time_add( + gpr_now(GPR_CLOCK_MONOTONIC), + gpr_time_from_nanos( + grpc_test_slowdown_factor() * static_cast(1e3) * time_micros, + GPR_TIMESPAN)); +} + void grpc_test_init(int /*argc*/, char** /*argv*/) { install_crash_handler(); gpr_log(GPR_DEBUG, diff --git a/test/core/util/test_config.h b/test/core/util/test_config.h index 112af3176f9..045caf4c6fb 100644 --- a/test/core/util/test_config.h +++ b/test/core/util/test_config.h @@ -33,6 +33,9 @@ gpr_timespec grpc_timeout_seconds_to_deadline(int64_t time_s); /* Converts a given timeout (in milliseconds) to a deadline. */ gpr_timespec grpc_timeout_milliseconds_to_deadline(int64_t time_ms); +/* Converts a given timeout (in microseconds) to a deadline. */ +gpr_timespec grpc_timeout_microseconds_to_deadline(int64_t time_micros); + #if !defined(GRPC_TEST_CUSTOM_PICK_PORT) && !defined(GRPC_PORT_ISOLATED_RUNTIME) #define GRPC_TEST_PICK_PORT #endif diff --git a/test/cpp/util/BUILD b/test/cpp/util/BUILD index 2a8ad6d9587..6790779d01f 100644 --- a/test/cpp/util/BUILD +++ b/test/cpp/util/BUILD @@ -122,12 +122,14 @@ grpc_cc_library( srcs = [ "cli_call.cc", "cli_credentials.cc", + "cli_flags.cc", "proto_file_parser.cc", "service_describer.cc", ], hdrs = [ "cli_call.h", "cli_credentials.h", + "cli_flags.h", "config_grpc_cli.h", "proto_file_parser.h", "service_describer.h", @@ -141,6 +143,7 @@ grpc_cc_library( ":grpc++_proto_reflection_desc_db", "//:grpc++", "//src/proto/grpc/reflection/v1alpha:reflection_proto", + "//test/core/util:grpc_test_util", ], ) diff --git a/test/cpp/util/cli_call.cc b/test/cpp/util/cli_call.cc index 049588f860f..fc90df07724 100644 --- a/test/cpp/util/cli_call.cc +++ b/test/cpp/util/cli_call.cc @@ -19,6 +19,7 @@ #include "test/cpp/util/cli_call.h" #include +#include #include #include @@ -28,6 +29,8 @@ #include #include +#include "test/core/util/test_config.h" + namespace grpc { namespace testing { namespace { @@ -61,6 +64,13 @@ CliCall::CliCall(const std::shared_ptr& channel, ctx_.AddMetadata(iter->first, iter->second); } } + + // Set deadline if timeout > 0 (default value -1 if no timeout specified) + if (FLAGS_timeout > 0){ + int64_t timeout_in_us = ceil(FLAGS_timeout * 1e6); + ctx_.set_deadline(grpc_timeout_microseconds_to_deadline(timeout_in_us)); + } + call_ = stub_->PrepareCall(&ctx_, method, &cq_); call_->StartCall(tag(1)); void* got_tag; diff --git a/test/cpp/util/cli_call.h b/test/cpp/util/cli_call.h index 4afa25d194e..a0adf875dfb 100644 --- a/test/cpp/util/cli_call.h +++ b/test/cpp/util/cli_call.h @@ -27,6 +27,8 @@ #include #include +#include "test/cpp/util/cli_flags.h" + namespace grpc { class ClientContext; diff --git a/test/cpp/util/cli_flags.cc b/test/cpp/util/cli_flags.cc new file mode 100644 index 00000000000..fdf910d4dbb --- /dev/null +++ b/test/cpp/util/cli_flags.cc @@ -0,0 +1,53 @@ +/* + * + * Copyright 2016 gRPC 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 + * + * http://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. + * + */ + +#include "test/cpp/util/cli_flags.h" + +namespace grpc { +namespace testing { + +// Define all flags used in gRPC cli. +DEFINE_bool(l, false, "Use a long listing format"); +DEFINE_bool(remotedb, true, "Use server types to parse and format messages"); +DEFINE_string(metadata, "", + "Metadata to send to server, in the form of key1:val1:key2:val2"); +DEFINE_string(proto_path, ".", "Path to look for the proto file."); +DEFINE_string(protofiles, "", "Name of the proto file."); +DEFINE_bool(binary_input, false, "Input in binary format"); +DEFINE_bool(binary_output, false, "Output in binary format"); +DEFINE_string( + default_service_config, "", + "Default service config to use on the channel, if non-empty. Note that " + "this will be ignored if the name resolver returns a service config."); +DEFINE_bool(display_peer_address, false, + "Log the peer socket address of the connection that each RPC is " + "made on to stderr."); +DEFINE_bool(json_input, false, "Input in json format"); +DEFINE_bool(json_output, false, "Output in json format"); +DEFINE_string(infile, "", "Input file (default is stdin)"); +DEFINE_bool(batch, false, + "Input contains multiple requests. Please do not use this to send " + "more than a few RPCs. gRPC CLI has very different performance " + "characteristics compared with normal RPC calls which make it " + "unsuitable for loadtesting or significant production traffic."); +DEFINE_double(timeout, -1, + "Specify timeout in seconds, used to set the deadline for all " + "RPCs. The default value of -1 means no deadline has been set."); + +} // namespace testing +} // namespace grpc diff --git a/test/cpp/util/cli_flags.h b/test/cpp/util/cli_flags.h new file mode 100644 index 00000000000..7a831bb8dab --- /dev/null +++ b/test/cpp/util/cli_flags.h @@ -0,0 +1,47 @@ +/* + * + * Copyright 2016 gRPC 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 + * + * http://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. + * + */ + +#ifndef GRPC_TEST_CPP_UTIL_CLI_FLAGS_H +#define GRPC_TEST_CPP_UTIL_CLI_FLAGS_H + +#include + +namespace grpc { +namespace testing { + +// Declare all flags used in gRPC cli. +DECLARE_bool(l); +DECLARE_bool(remotedb); +DECLARE_string(metadata); +DECLARE_string(proto_path); +DECLARE_string(protofiles); +DECLARE_bool(binary_input); +DECLARE_bool(binary_output); +DECLARE_string(default_service_config); +DECLARE_bool(display_peer_address); +DECLARE_bool(json_input); +DECLARE_bool(json_output); +DECLARE_string(infile); +DECLARE_bool(batch); +DECLARE_double(timeout); + +} // namespace testing +} // namespace grpc + +#endif // GRPC_TEST_CPP_UTIL_CLI_FLAGS_H + diff --git a/test/cpp/util/grpc_tool.cc b/test/cpp/util/grpc_tool.cc index 27a9bdc3665..7a2afee810f 100644 --- a/test/cpp/util/grpc_tool.cc +++ b/test/cpp/util/grpc_tool.cc @@ -26,7 +26,6 @@ #include #include -#include #include #include #include @@ -49,32 +48,6 @@ namespace grpc { namespace testing { -DEFINE_bool(l, false, "Use a long listing format"); -DEFINE_bool(remotedb, true, "Use server types to parse and format messages"); -DEFINE_string(metadata, "", - "Metadata to send to server, in the form of key1:val1:key2:val2"); -DEFINE_string(proto_path, ".", "Path to look for the proto file."); -DEFINE_string(protofiles, "", "Name of the proto file."); -DEFINE_bool(binary_input, false, "Input in binary format"); -DEFINE_bool(binary_output, false, "Output in binary format"); -DEFINE_string( - default_service_config, "", - "Default service config to use on the channel, if non-empty. Note " - "that this will be ignored if the name resolver returns a service " - "config."); -DEFINE_bool( - display_peer_address, false, - "Log the peer socket address of the connection that each RPC is made " - "on to stderr."); -DEFINE_bool(json_input, false, "Input in json format"); -DEFINE_bool(json_output, false, "Output in json format"); -DEFINE_string(infile, "", "Input file (default is stdin)"); -DEFINE_bool(batch, false, - "Input contains multiple requests. Please do not use this to send " - "more than a few RPCs. gRPC CLI has very different performance " - "characteristics compared with normal RPC calls which make it " - "unsuitable for loadtesting or significant production traffic."); - namespace { class GrpcTool { @@ -490,7 +463,10 @@ bool GrpcTool::CallMethod(int argc, const char** argv, " --binary_input ; Input in binary format\n" " --binary_output ; Output in binary format\n" " --json_input ; Input in json format\n" - " --json_output ; Output in json format\n" + + " --json_output ; Output in json format\n" + " --timeout ; Specify timeout (in seconds), used to " + "set the deadline for RPCs. The default value of -1 means no " + "deadline has been set.\n" + cred.GetCredentialUsage()); std::stringstream output_ss; diff --git a/test/cpp/util/grpc_tool.h b/test/cpp/util/grpc_tool.h index e998807cc61..9da2c6a2702 100644 --- a/test/cpp/util/grpc_tool.h +++ b/test/cpp/util/grpc_tool.h @@ -24,6 +24,7 @@ #include #include "test/cpp/util/cli_credentials.h" +#include "test/cpp/util/cli_flags.h" namespace grpc { namespace testing { diff --git a/test/cpp/util/grpc_tool_test.cc b/test/cpp/util/grpc_tool_test.cc index c98a7d1d2a8..b12ddd88a67 100644 --- a/test/cpp/util/grpc_tool_test.cc +++ b/test/cpp/util/grpc_tool_test.cc @@ -18,7 +18,6 @@ #include "test/cpp/util/grpc_tool.h" -#include #include #include #include @@ -54,6 +53,8 @@ using grpc::testing::EchoResponse; "Echo\n" \ "Echo1\n" \ "Echo2\n" \ + "CheckDeadlineUpperBound\n" \ + "CheckDeadlineSet\n" \ "CheckClientInitialMetadata\n" \ "RequestStream\n" \ "ResponseStream\n" \ @@ -70,6 +71,10 @@ using grpc::testing::EchoResponse; "{}\n" \ " rpc Echo2(grpc.testing.EchoRequest) returns (grpc.testing.EchoResponse) " \ "{}\n" \ + " rpc CheckDeadlineUpperBound(grpc.testing.SimpleRequest) returns " \ + "(grpc.testing.StringValue) {}\n" \ + " rpc CheckDeadlineSet(grpc.testing.SimpleRequest) returns " \ + "(grpc.testing.StringValue) {}\n" \ " rpc CheckClientInitialMetadata(grpc.testing.SimpleRequest) returns " \ "(grpc.testing.SimpleResponse) {}\n" \ " rpc RequestStream(stream grpc.testing.EchoRequest) returns " \ @@ -109,17 +114,6 @@ DECLARE_string(ssl_target); namespace grpc { namespace testing { -DECLARE_bool(binary_input); -DECLARE_bool(binary_output); -DECLARE_bool(json_input); -DECLARE_bool(json_output); -DECLARE_bool(l); -DECLARE_bool(batch); -DECLARE_string(metadata); -DECLARE_string(protofiles); -DECLARE_string(proto_path); -DECLARE_string(default_service_config); - namespace { const int kServerDefaultResponseStreamsToSend = 3; @@ -177,6 +171,30 @@ class TestServiceImpl : public ::grpc::testing::EchoTestService::Service { return Status::OK; } + Status CheckDeadlineSet(ServerContext* context, const SimpleRequest* request, + StringValue* response) override { + response->set_message(context->deadline() != + std::chrono::system_clock::time_point::max() + ? "true" + : "false"); + return Status::OK; + } + + // Check if deadline - current time <= timeout + // If deadline set, timeout + current time should be an upper bound for it + Status CheckDeadlineUpperBound(ServerContext* context, + const SimpleRequest* request, + StringValue* response) override { + auto seconds = std::chrono::duration_cast( + context->deadline() - std::chrono::system_clock::now()); + + // Returning string instead of bool to avoid using embedded messages in + // proto3 + response->set_message(seconds.count() <= FLAGS_timeout ? "true" + : "false"); + return Status::OK; + } + Status RequestStream(ServerContext* context, ServerReader* reader, EchoResponse* response) override { @@ -862,6 +880,93 @@ TEST_F(GrpcToolTest, CallCommandRequestStreamWithBadRequestJsonInput) { ShutdownServer(); } +TEST_F(GrpcToolTest, CallCommandWithTimeoutDeadlineSet) { + // Test input "grpc_cli call CheckDeadlineSet --timeout=5000.25" + std::stringstream output_stream; + + const std::string server_address = SetUpServer(); + const char* argv[] = {"grpc_cli", "call", server_address.c_str(), + "CheckDeadlineSet"}; + + // Set timeout to 5000.25 seconds + FLAGS_timeout = 5000.25; + + EXPECT_TRUE(0 == GrpcToolMainLib(ArraySize(argv), argv, TestCliCredentials(), + std::bind(PrintStream, &output_stream, + std::placeholders::_1))); + + // Expected output: "message: "true"", deadline set + EXPECT_TRUE(nullptr != + strstr(output_stream.str().c_str(), "message: \"true\"")); + ShutdownServer(); +} + +TEST_F(GrpcToolTest, CallCommandWithTimeoutDeadlineUpperBound) { + // Test input "grpc_cli call CheckDeadlineUpperBound --timeout=900" + std::stringstream output_stream; + + const std::string server_address = SetUpServer(); + const char* argv[] = {"grpc_cli", "call", server_address.c_str(), + "CheckDeadlineUpperBound"}; + + // Set timeout to 900 seconds + FLAGS_timeout = 900; + + EXPECT_TRUE(0 == GrpcToolMainLib(ArraySize(argv), argv, TestCliCredentials(), + std::bind(PrintStream, &output_stream, + std::placeholders::_1))); + + // Expected output: "message: "true"" + // deadline not greater than timeout + current time + EXPECT_TRUE(nullptr != + strstr(output_stream.str().c_str(), "message: \"true\"")); + ShutdownServer(); +} + +TEST_F(GrpcToolTest, CallCommandWithNegativeTimeoutValue) { + // Test input "grpc_cli call CheckDeadlineSet --timeout=-5" + std::stringstream output_stream; + + const std::string server_address = SetUpServer(); + const char* argv[] = {"grpc_cli", "call", server_address.c_str(), + "CheckDeadlineSet"}; + + // Set timeout to -5 (deadline not set) + FLAGS_timeout = -5; + + EXPECT_TRUE(0 == GrpcToolMainLib(ArraySize(argv), argv, TestCliCredentials(), + std::bind(PrintStream, &output_stream, + std::placeholders::_1))); + + // Expected output: "message: "false"", deadline not set + EXPECT_TRUE(nullptr != + strstr(output_stream.str().c_str(), "message: \"false\"")); + + ShutdownServer(); +} + +TEST_F(GrpcToolTest, CallCommandWithDefaultTimeoutValue) { + // Test input "grpc_cli call CheckDeadlineSet --timeout=-1" + std::stringstream output_stream; + + const std::string server_address = SetUpServer(); + const char* argv[] = {"grpc_cli", "call", server_address.c_str(), + "CheckDeadlineSet"}; + + // Set timeout to -1 (default value, deadline not set) + FLAGS_timeout = -1; + + EXPECT_TRUE(0 == GrpcToolMainLib(ArraySize(argv), argv, TestCliCredentials(), + std::bind(PrintStream, &output_stream, + std::placeholders::_1))); + + // Expected output: "message: "false"", deadline not set + EXPECT_TRUE(nullptr != + strstr(output_stream.str().c_str(), "message: \"false\"")); + + ShutdownServer(); +} + TEST_F(GrpcToolTest, CallCommandResponseStream) { // Test input: grpc_cli call localhost: ResponseStream "message: // 'Hello'"