// // // Copyright 2019 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 #include #include #include "absl/log/check.h" #include "absl/memory/memory.h" #include #include #include #include #include #include #include #include #include #include "src/core/lib/gprpp/crash.h" #include "src/core/lib/iomgr/timer.h" #include "src/proto/grpc/testing/echo.grpc.pb.h" #include "test/core/test_util/port.h" #include "test/core/test_util/test_config.h" #include "test/cpp/end2end/test_service_impl.h" #include "test/cpp/util/subprocess.h" static std::string g_root; static gpr_mu g_mu; extern gpr_timespec (*gpr_now_impl)(gpr_clock_type clock_type); gpr_timespec (*gpr_now_impl_orig)(gpr_clock_type clock_type) = gpr_now_impl; static int g_time_shift_sec = 0; static int g_time_shift_nsec = 0; static gpr_timespec now_impl(gpr_clock_type clock) { auto ts = gpr_now_impl_orig(clock); // We only manipulate the realtime clock to simulate changes in wall-clock // time if (clock != GPR_CLOCK_REALTIME) { return ts; } CHECK_GE(ts.tv_nsec, 0); CHECK_LT(ts.tv_nsec, GPR_NS_PER_SEC); gpr_mu_lock(&g_mu); ts.tv_sec += g_time_shift_sec; ts.tv_nsec += g_time_shift_nsec; gpr_mu_unlock(&g_mu); if (ts.tv_nsec >= GPR_NS_PER_SEC) { ts.tv_nsec -= GPR_NS_PER_SEC; ++ts.tv_sec; } else if (ts.tv_nsec < 0) { --ts.tv_sec; ts.tv_nsec = GPR_NS_PER_SEC + ts.tv_nsec; } return ts; } // offset the value returned by gpr_now(GPR_CLOCK_REALTIME) by msecs // milliseconds static void set_now_offset(int msecs) { gpr_mu_lock(&g_mu); g_time_shift_sec = msecs / 1000; g_time_shift_nsec = (msecs % 1000) * 1e6; gpr_mu_unlock(&g_mu); } // restore the original implementation of gpr_now() static void reset_now_offset() { gpr_mu_lock(&g_mu); g_time_shift_sec = 0; g_time_shift_nsec = 0; gpr_mu_unlock(&g_mu); } namespace grpc { namespace testing { namespace { // gpr_now() is called with invalid clock_type TEST(TimespecTest, GprNowInvalidClockType) { // initialize to some junk value gpr_clock_type invalid_clock_type = static_cast(32641); EXPECT_DEATH(gpr_now(invalid_clock_type), ".*"); } // Add timespan with negative nanoseconds TEST(TimespecTest, GprTimeAddNegativeNs) { gpr_timespec now = gpr_now(GPR_CLOCK_MONOTONIC); gpr_timespec bad_ts = {1, -1000, GPR_TIMESPAN}; EXPECT_DEATH(gpr_time_add(now, bad_ts), ".*"); } // Subtract timespan with negative nanoseconds TEST(TimespecTest, GprTimeSubNegativeNs) { // Nanoseconds must always be positive. Negative timestamps are represented by // (negative seconds, positive nanoseconds) gpr_timespec now = gpr_now(GPR_CLOCK_MONOTONIC); gpr_timespec bad_ts = {1, -1000, GPR_TIMESPAN}; EXPECT_DEATH(gpr_time_sub(now, bad_ts), ".*"); } // Add negative milliseconds to gpr_timespec TEST(TimespecTest, GrpcNegativeMillisToTimespec) { // -1500 milliseconds converts to timespec (-2 secs, 5 * 10^8 nsec) gpr_timespec ts = grpc_core::Timestamp::FromMillisecondsAfterProcessEpoch(-1500) .as_timespec(GPR_CLOCK_MONOTONIC); CHECK(ts.tv_sec = -2); CHECK(ts.tv_nsec = 5e8); CHECK_EQ(ts.clock_type, GPR_CLOCK_MONOTONIC); } class TimeChangeTest : public ::testing::Test { protected: TimeChangeTest() {} static void SetUpTestSuite() { auto port = grpc_pick_unused_port_or_die(); std::ostringstream addr_stream; addr_stream << "localhost:" << port; server_address_ = addr_stream.str(); server_ = std::make_unique(std::vector({ g_root + "/client_crash_test_server", "--address=" + server_address_, })); CHECK(server_); // connect to server and make sure it's reachable. auto channel = grpc::CreateChannel(server_address_, InsecureChannelCredentials()); CHECK(channel); EXPECT_TRUE(channel->WaitForConnected( grpc_timeout_milliseconds_to_deadline(30000))); } static void TearDownTestSuite() { server_.reset(); } void SetUp() override { channel_ = grpc::CreateChannel(server_address_, InsecureChannelCredentials()); CHECK(channel_); stub_ = grpc::testing::EchoTestService::NewStub(channel_); } void TearDown() override { reset_now_offset(); } std::unique_ptr CreateStub() { return grpc::testing::EchoTestService::NewStub(channel_); } std::shared_ptr GetChannel() { return channel_; } // time jump offsets in milliseconds const int TIME_OFFSET1 = 20123; const int TIME_OFFSET2 = 5678; private: static std::string server_address_; static std::unique_ptr server_; std::shared_ptr channel_; std::unique_ptr stub_; }; std::string TimeChangeTest::server_address_; std::unique_ptr TimeChangeTest::server_; // Wall-clock time jumps forward on client before bidi stream is created TEST_F(TimeChangeTest, TimeJumpForwardBeforeStreamCreated) { EchoRequest request; EchoResponse response; ClientContext context; context.set_deadline(grpc_timeout_milliseconds_to_deadline(5000)); context.AddMetadata(kServerResponseStreamsToSend, "1"); auto channel = GetChannel(); CHECK(channel); EXPECT_TRUE( channel->WaitForConnected(grpc_timeout_milliseconds_to_deadline(5000))); auto stub = CreateStub(); // time jumps forward by TIME_OFFSET1 milliseconds set_now_offset(TIME_OFFSET1); auto stream = stub->BidiStream(&context); request.set_message("Hello"); EXPECT_TRUE(stream->Write(request)); EXPECT_TRUE(stream->WritesDone()); EXPECT_TRUE(stream->Read(&response)); auto status = stream->Finish(); EXPECT_TRUE(status.ok()); } // Wall-clock time jumps back on client before bidi stream is created TEST_F(TimeChangeTest, TimeJumpBackBeforeStreamCreated) { EchoRequest request; EchoResponse response; ClientContext context; context.set_deadline(grpc_timeout_milliseconds_to_deadline(5000)); context.AddMetadata(kServerResponseStreamsToSend, "1"); auto channel = GetChannel(); CHECK(channel); EXPECT_TRUE( channel->WaitForConnected(grpc_timeout_milliseconds_to_deadline(5000))); auto stub = CreateStub(); // time jumps back by TIME_OFFSET1 milliseconds set_now_offset(-TIME_OFFSET1); auto stream = stub->BidiStream(&context); request.set_message("Hello"); EXPECT_TRUE(stream->Write(request)); EXPECT_TRUE(stream->WritesDone()); EXPECT_TRUE(stream->Read(&response)); EXPECT_EQ(request.message(), response.message()); auto status = stream->Finish(); EXPECT_TRUE(status.ok()); } // Wall-clock time jumps forward on client while call is in progress TEST_F(TimeChangeTest, TimeJumpForwardAfterStreamCreated) { EchoRequest request; EchoResponse response; ClientContext context; context.set_deadline(grpc_timeout_milliseconds_to_deadline(5000)); context.AddMetadata(kServerResponseStreamsToSend, "2"); auto channel = GetChannel(); CHECK(channel); EXPECT_TRUE( channel->WaitForConnected(grpc_timeout_milliseconds_to_deadline(5000))); auto stub = CreateStub(); auto stream = stub->BidiStream(&context); request.set_message("Hello"); EXPECT_TRUE(stream->Write(request)); EXPECT_TRUE(stream->Read(&response)); // time jumps forward by TIME_OFFSET1 milliseconds. set_now_offset(TIME_OFFSET1); request.set_message("World"); EXPECT_TRUE(stream->Write(request)); EXPECT_TRUE(stream->WritesDone()); EXPECT_TRUE(stream->Read(&response)); auto status = stream->Finish(); EXPECT_TRUE(status.ok()); } // Wall-clock time jumps back on client while call is in progress TEST_F(TimeChangeTest, TimeJumpBackAfterStreamCreated) { EchoRequest request; EchoResponse response; ClientContext context; context.set_deadline(grpc_timeout_milliseconds_to_deadline(5000)); context.AddMetadata(kServerResponseStreamsToSend, "2"); auto channel = GetChannel(); CHECK(channel); EXPECT_TRUE( channel->WaitForConnected(grpc_timeout_milliseconds_to_deadline(5000))); auto stub = CreateStub(); auto stream = stub->BidiStream(&context); request.set_message("Hello"); EXPECT_TRUE(stream->Write(request)); EXPECT_TRUE(stream->Read(&response)); // time jumps back TIME_OFFSET1 milliseconds. set_now_offset(-TIME_OFFSET1); request.set_message("World"); EXPECT_TRUE(stream->Write(request)); EXPECT_TRUE(stream->WritesDone()); EXPECT_TRUE(stream->Read(&response)); auto status = stream->Finish(); EXPECT_TRUE(status.ok()); } // Wall-clock time jumps forward and backwards during call TEST_F(TimeChangeTest, TimeJumpForwardAndBackDuringCall) { EchoRequest request; EchoResponse response; ClientContext context; context.set_deadline(grpc_timeout_milliseconds_to_deadline(5000)); context.AddMetadata(kServerResponseStreamsToSend, "2"); auto channel = GetChannel(); CHECK(channel); EXPECT_TRUE( channel->WaitForConnected(grpc_timeout_milliseconds_to_deadline(5000))); auto stub = CreateStub(); auto stream = stub->BidiStream(&context); request.set_message("Hello"); EXPECT_TRUE(stream->Write(request)); // time jumps back by TIME_OFFSET2 milliseconds set_now_offset(-TIME_OFFSET2); EXPECT_TRUE(stream->Read(&response)); request.set_message("World"); // time jumps forward by TIME_OFFSET milliseconds set_now_offset(TIME_OFFSET1); EXPECT_TRUE(stream->Write(request)); // time jumps back by TIME_OFFSET2 milliseconds set_now_offset(-TIME_OFFSET2); EXPECT_TRUE(stream->WritesDone()); // time jumps back by TIME_OFFSET2 milliseconds set_now_offset(-TIME_OFFSET2); EXPECT_TRUE(stream->Read(&response)); // time jumps back by TIME_OFFSET2 milliseconds set_now_offset(-TIME_OFFSET2); auto status = stream->Finish(); EXPECT_TRUE(status.ok()); } } // namespace } // namespace testing } // namespace grpc int main(int argc, char** argv) { std::string me = argv[0]; // get index of last slash in path to test binary auto lslash = me.rfind('/'); // set g_root = path to directory containing test binary if (lslash != std::string::npos) { g_root = me.substr(0, lslash); } else { g_root = "."; } gpr_mu_init(&g_mu); gpr_now_impl = now_impl; grpc::testing::TestEnvironment env(&argc, argv); ::testing::InitGoogleTest(&argc, argv); auto ret = RUN_ALL_TESTS(); return ret; }