// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "remoting/base/telemetry_log_writer.h" #include #include "base/bind.h" #include "base/callback_helpers.h" #include "base/containers/circular_deque.h" #include "base/logging.h" #include "base/memory/ptr_util.h" #include "base/run_loop.h" #include "base/test/task_environment.h" #include "base/timer/timer.h" #include "net/http/http_status_code.h" #include "remoting/base/chromoting_event.h" #include "remoting/base/fake_oauth_token_getter.h" #include "remoting/base/protobuf_http_status.h" #include "remoting/base/protobuf_http_test_responder.h" #include "remoting/proto/remoting/v1/telemetry_messages.pb.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace remoting { namespace { MATCHER_P(HasDurations, durations, "") { apis::v1::CreateEventRequest request; EXPECT_TRUE(ProtobufHttpTestResponder::ParseRequestMessage(arg, &request)); if (!request.has_payload() || static_cast(request.payload().events_size()) != durations.size()) { return false; } for (std::size_t i = 0; i < durations.size(); ++i) { auto event = request.payload().events(i); if (!event.has_session_duration() || event.session_duration() != durations[i]) { return false; } } return true; } template std::array MakeIntArray(Args&&... args) { return {std::forward(args)...}; } // Sets expectation for call to CreateEvent with the set of events specified, // identified by their session_duration field. (Session duration is incremented // after each call to LogFakeEvent.) // // responder: The ProtobufHttpTestResponder on which to set the expectation. // durations: The durations of the expected events, grouped with parentheses. // E.g., (0) or (1, 2). // // Example usage: // EXPECT_EVENTS(test_responder_, (1, 2)) // .WillOnce(DoSucceed(&test_responder_)); #define EXPECT_EVENTS(responder, durations) \ EXPECT_CALL((responder.GetMockInterceptor()), \ Run(HasDurations(MakeIntArray durations))) // Creates a success action to be passed to WillOnce and friends. decltype(auto) DoSucceed(ProtobufHttpTestResponder* responder) { return [responder](const network::ResourceRequest& request) { return responder->AddResponse(request.url.spec(), apis::v1::CreateEventResponse()); }; } // Creates a failure action to be passed to WillOnce and friends. decltype(auto) DoFail(ProtobufHttpTestResponder* responder) { return [responder](const network::ResourceRequest& request) { return responder->AddError( request.url.spec(), ProtobufHttpStatus(ProtobufHttpStatus::Code::UNAVAILABLE, "The service is unavailable.")); }; } } // namespace class TelemetryLogWriterTest : public testing::Test { public: TelemetryLogWriterTest() { log_writer_.Init(test_responder_.GetUrlLoaderFactory()); } ~TelemetryLogWriterTest() override { // It's an async process to create request to send all pending events. RunUntilIdle(); } protected: void LogFakeEvent() { ChromotingEvent entry; entry.SetInteger(ChromotingEvent::kSessionDurationKey, duration_); duration_++; log_writer_.Log(entry); } // Waits until TelemetryLog is idle. void RunUntilIdle() { // gRPC has its own event loop, which means sometimes the task queue will // be empty while gRPC is working. Thus, TaskEnvironment::RunUntilIdle can't // be used, as it would return early. Instead, TelemetryLogWriter is polled // to determine when it has finished. base::RunLoop run_loop; base::RepeatingTimer timer; // Mock clock will auto-fast-forward, so the delay here is somewhat // arbitrary. timer.Start( FROM_HERE, base::Seconds(1), base::BindRepeating( [](TelemetryLogWriter* log_writer, base::RepeatingClosure quit_closure) { if (log_writer->IsIdleForTesting()) { quit_closure.Run(); } }, base::Unretained(&log_writer_), run_loop.QuitWhenIdleClosure())); run_loop.Run(); } ProtobufHttpTestResponder test_responder_; TelemetryLogWriter log_writer_{ std::make_unique(OAuthTokenGetter::SUCCESS, "dummy", "dummy")}; private: // Incremented for each event to allow them to be distinguished. int duration_ = 0; // MOCK_TIME will fast forward through back-off delays. base::test::SingleThreadTaskEnvironment task_environment_{ base::test::SingleThreadTaskEnvironment::TimeSource::MOCK_TIME}; }; TEST_F(TelemetryLogWriterTest, PostOneLogImmediately) { EXPECT_EVENTS(test_responder_, (0)).WillOnce(DoSucceed(&test_responder_)); LogFakeEvent(); } TEST_F(TelemetryLogWriterTest, PostOneLogAndHaveTwoPendingLogs) { ::testing::InSequence sequence; // First one is sent right away. Second two are batched and sent once the // first request has completed. EXPECT_EVENTS(test_responder_, (0)).WillOnce(DoSucceed(&test_responder_)); EXPECT_EVENTS(test_responder_, (1, 2)).WillOnce(DoSucceed(&test_responder_)); LogFakeEvent(); LogFakeEvent(); LogFakeEvent(); } TEST_F(TelemetryLogWriterTest, PostLogFailedAndRetry) { EXPECT_EVENTS(test_responder_, (0)) .Times(5) .WillRepeatedly(DoFail(&test_responder_)); LogFakeEvent(); } TEST_F(TelemetryLogWriterTest, PostOneLogFailedResendWithTwoPendingLogs) { EXPECT_EVENTS(test_responder_, (0)).WillOnce(DoFail(&test_responder_)); EXPECT_EVENTS(test_responder_, (0, 1, 2)) .WillOnce(DoSucceed(&test_responder_)); LogFakeEvent(); LogFakeEvent(); LogFakeEvent(); } TEST_F(TelemetryLogWriterTest, PostThreeLogsFailedAndResendWithOnePending) { // This tests the ordering of the resent log. EXPECT_EVENTS(test_responder_, (0)).WillOnce(DoFail(&test_responder_)); EXPECT_EVENTS(test_responder_, (0, 1, 2)) .WillOnce(testing::DoAll( testing::InvokeWithoutArgs([this]() { LogFakeEvent(); }), DoFail(&test_responder_))); EXPECT_EVENTS(test_responder_, (0, 1, 2, 3)) .WillOnce(DoSucceed(&test_responder_)); LogFakeEvent(); LogFakeEvent(); LogFakeEvent(); } TEST_F(TelemetryLogWriterTest, PostOneFailedThenSucceed) { EXPECT_EVENTS(test_responder_, (0)) .WillOnce(DoFail(&test_responder_)) .WillOnce(DoSucceed(&test_responder_)); LogFakeEvent(); } } // namespace remoting