| // Copyright 2018 The Chromium OS 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 <utility> |
| |
| #include <base/message_loop/message_loop.h> |
| #include <base/time/time.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| #include "diagnostics/telem/async_grpc_client_adapter.h" |
| #include "diagnostics/telem/telem_connection.h" |
| #include "diagnostics/telem/telemetry_item_enum.h" |
| |
| using ::testing::_; |
| using ::testing::Invoke; |
| using ::testing::StrictMock; |
| using ::testing::WithArg; |
| |
| namespace diagnostics { |
| |
| namespace { |
| |
| using ProcDataCallback = base::Callback<void( |
| std::unique_ptr<grpc_api::GetProcDataResponse> response)>; |
| using SysfsDataCallback = base::Callback<void( |
| std::unique_ptr<grpc_api::GetSysfsDataResponse> response)>; |
| |
| constexpr char kFakeMeminfoFileContents[] = |
| "MemTotal: 3906320 kB\nMemFree: 873180 kB\n"; |
| constexpr char kFakeBadMeminfoFileContents[] = |
| "MemTotal: 3906320\nMemfree: 873180 kB\n"; |
| constexpr int kFakeMemTotalMebibytes = 3814; |
| constexpr int kFakeMemFreeMebibytes = 852; |
| constexpr char kFakeLoadavgFileContents[] = "0.82 0.61 0.52 2/370 30707\n"; |
| constexpr char kFakeBadLoadavgFileContents[] = "0.82 0.61 0.52 2 370 30707\n"; |
| constexpr int kFakeNumRunnableEntities = 2; |
| constexpr int kFakeNumExistingEntities = 370; |
| constexpr char kFakeStatFileContents[] = |
| "cpu 0 0 0 165432156432413546\ncpu0 0 0 0 653435243543\ncpu1 0 0 0 " |
| "235435413\nctxt 5345634354"; |
| constexpr char kFakeBadStatFileContents[] = |
| "cpu 0 0 165432156432413546\npu0 0 0 0 653435243543\ncpu1 0 0 0 " |
| "235435413\nctxt 5345634354"; |
| constexpr char kFakeTotalCPUIdleTime[] = "165432156432413546"; |
| char const* kFakePerCPUIdleTimes[] = {"653435243543", "235435413"}; |
| |
| class MockAsyncGrpcClientAdapter : public AsyncGrpcClientAdapter { |
| public: |
| void Connect(const std::string& target_uri) override {} |
| |
| MOCK_CONST_METHOD0(IsConnected, bool()); |
| MOCK_METHOD1(Shutdown, void(const base::Closure& on_shutdown)); |
| MOCK_METHOD2(GetProcData, |
| void(const grpc_api::GetProcDataRequest& request, |
| ProcDataCallback callback)); |
| MOCK_METHOD2(GetSysfsData, |
| void(const grpc_api::GetSysfsDataRequest& request, |
| SysfsDataCallback callback)); |
| }; |
| |
| } // namespace |
| |
| class TelemConnectionTest : public ::testing::Test { |
| protected: |
| TelemConnectionTest() { |
| EXPECT_CALL(mock_adapter_, Shutdown(_)) |
| .WillOnce(Invoke( |
| [](const base::Closure& on_shutdown) { on_shutdown.Run(); })); |
| } |
| |
| void SetConnection(std::unique_ptr<TelemConnection> connection) { |
| connection_ = std::move(connection); |
| } |
| |
| // Sets the MockAsyncGrpcClientAdapter to run the next callback with |
| // a null pointer as its response. |
| void SetNullptrProcDataResponse() { |
| EXPECT_CALL(mock_adapter_, GetProcData(_, _)) |
| .WillOnce(WithArg<1>( |
| Invoke([](ProcDataCallback callback) { callback.Run(nullptr); }))); |
| } |
| |
| // Sets the MockAsyncGrpcClientAdapter to run the next callback with a |
| // fake file dump as its ProcDataResponse. |
| void SetProcDataResponse(const std::string& file_contents) { |
| std::unique_ptr<grpc_api::GetProcDataResponse> response = |
| std::make_unique<grpc_api::GetProcDataResponse>(); |
| // response->mutable_file_dump()->Add() |
| EXPECT_CALL(mock_adapter_, GetProcData(_, _)) |
| .WillRepeatedly( |
| Invoke([file_contents](::testing::Unused, |
| ProcDataCallback callback) mutable { |
| std::unique_ptr<grpc_api::GetProcDataResponse> reply = |
| std::make_unique<grpc_api::GetProcDataResponse>(); |
| grpc_api::FileDump fake_file_dump; |
| fake_file_dump.set_contents(file_contents); |
| reply->mutable_file_dump()->Add()->Swap(&fake_file_dump); |
| callback.Run(std::move(reply)); |
| })); |
| } |
| |
| // Checks whether the response matches the expected response from a |
| // GetItem(item) command. |item| must be expected as an integer. |
| void CheckIntegerItemResponse( |
| const base::Optional<base::Value>& actual_response, |
| TelemetryItemEnum item) { |
| int val; |
| |
| // Make sure that a value was returned, and that value has an integer |
| // representation. |
| ASSERT_TRUE(actual_response); |
| ASSERT_TRUE(actual_response.value().GetAsInteger(&val)); |
| switch (item) { |
| case TelemetryItemEnum::kMemTotalMebibytes: |
| EXPECT_EQ(val, kFakeMemTotalMebibytes); |
| break; |
| case TelemetryItemEnum::kMemFreeMebibytes: |
| EXPECT_EQ(val, kFakeMemFreeMebibytes); |
| break; |
| case TelemetryItemEnum::kNumRunnableEntities: |
| EXPECT_EQ(val, kFakeNumRunnableEntities); |
| break; |
| case TelemetryItemEnum::kNumExistingEntities: |
| EXPECT_EQ(val, kFakeNumExistingEntities); |
| break; |
| default: |
| // We should never get here, because all items currently tested |
| // which have integer representations are enumerated above. However, |
| // we need this default case, because some TelemetryItemEnums |
| // cannot be represented as integers and are not checked here. |
| break; |
| } |
| } |
| |
| // Checks whether the response matches the expected response from a |
| // GetItem(item) command. |item| must be expected as a string. |
| void CheckStringItemResponse( |
| const base::Optional<base::Value>& actual_response, |
| TelemetryItemEnum item) { |
| std::string val; |
| |
| // Make sure that a value was returned, and that value has a string |
| // representation. |
| ASSERT_TRUE(actual_response); |
| ASSERT_TRUE(actual_response.value().GetAsString(&val)); |
| switch (item) { |
| case TelemetryItemEnum::kTotalIdleTimeUserHz: |
| EXPECT_EQ(val, kFakeTotalCPUIdleTime); |
| break; |
| default: |
| // We should never get here, because all items currently tested |
| // which have string representations are enumerated above. However, |
| // we need this default case, because some TelemetryItemEnums |
| // cannot be represented as strings and are not checked here. |
| break; |
| } |
| } |
| |
| // Checks whether the response matches the expected response from a |
| // GetItem(item) command. |item| must be expected as a list. |
| void CheckListItemResponse(const base::Optional<base::Value>& actual_response, |
| TelemetryItemEnum item) { |
| const base::ListValue* list_val; |
| std::string val; |
| |
| // Make sure that a value was returned, and that value has a list |
| // representation. |
| ASSERT_TRUE(actual_response); |
| ASSERT_TRUE(actual_response.value().GetAsList(&list_val)); |
| switch (item) { |
| case TelemetryItemEnum::kIdleTimePerCPUUserHz: { |
| for (int i = 0; i < list_val->GetSize(); i++) { |
| ASSERT_TRUE(list_val->GetString(i, &val)); |
| EXPECT_EQ(val, kFakePerCPUIdleTimes[i]); |
| } |
| break; |
| } |
| default: { |
| // We should never get here, because all items currently tested |
| // which have list representations are enumerated above. However, |
| // we need this default case, because some TelemetryItemEnums |
| // cannot be represented as lists and are not checked here. |
| break; |
| } |
| } |
| } |
| |
| // Checks whether the response matches the expected response |
| // from a GetGroup(TelemetryGroupEnum::kDisk) command. |
| void CheckDiskGroupResponse( |
| const std::vector< |
| std::pair<TelemetryItemEnum, const base::Optional<base::Value>>>& |
| actual_response) { |
| // Indices where we expect MemTotal and MemFree to occur in the response. |
| constexpr int kMemTotalIndex = 0; |
| constexpr int kMemFreeIndex = 1; |
| // Number of items in the kDisk group. |
| constexpr int kNumDiskItems = 2; |
| |
| int val; |
| |
| EXPECT_EQ(actual_response.size(), kNumDiskItems); |
| ASSERT_EQ(actual_response[kMemTotalIndex].first, |
| TelemetryItemEnum::kMemTotalMebibytes); |
| ASSERT_TRUE(actual_response[kMemTotalIndex].second); |
| ASSERT_TRUE( |
| actual_response[kMemTotalIndex].second.value().GetAsInteger(&val)); |
| EXPECT_EQ(val, kFakeMemTotalMebibytes); |
| EXPECT_EQ(actual_response[kMemFreeIndex].first, |
| TelemetryItemEnum::kMemFreeMebibytes); |
| ASSERT_TRUE(actual_response[kMemFreeIndex].second); |
| ASSERT_TRUE( |
| actual_response[kMemFreeIndex].second.value().GetAsInteger(&val)); |
| EXPECT_EQ(val, kFakeMemFreeMebibytes); |
| } |
| |
| TelemConnection* connection() { return connection_.get(); } |
| |
| private: |
| base::MessageLoopForIO message_loop_; |
| StrictMock<MockAsyncGrpcClientAdapter> mock_adapter_; |
| std::unique_ptr<TelemConnection> connection_ = |
| std::make_unique<TelemConnection>(&mock_adapter_); |
| |
| DISALLOW_COPY_AND_ASSIGN(TelemConnectionTest); |
| }; |
| |
| // Test that we catch an error response. |
| TEST_F(TelemConnectionTest, NullptrResponse) { |
| SetNullptrProcDataResponse(); |
| |
| EXPECT_EQ(connection()->GetItem(TelemetryItemEnum::kMemTotalMebibytes, |
| base::TimeDelta()), |
| base::nullopt); |
| } |
| |
| // Test that we can retrieve kMemTotalMebibytes. |
| TEST_F(TelemConnectionTest, GetMemTotal) { |
| SetProcDataResponse(kFakeMeminfoFileContents); |
| |
| auto mem_total = connection()->GetItem(TelemetryItemEnum::kMemTotalMebibytes, |
| base::TimeDelta()); |
| CheckIntegerItemResponse(mem_total, TelemetryItemEnum::kMemTotalMebibytes); |
| } |
| |
| // Test that we can retrieve kMemFreeMebibytes. |
| TEST_F(TelemConnectionTest, GetMemFree) { |
| SetProcDataResponse(kFakeMeminfoFileContents); |
| |
| auto mem_free = connection()->GetItem(TelemetryItemEnum::kMemFreeMebibytes, |
| base::TimeDelta()); |
| CheckIntegerItemResponse(mem_free, TelemetryItemEnum::kMemFreeMebibytes); |
| } |
| |
| // Test that an incorrectly formatted /proc/meminfo will fail to parse. |
| TEST_F(TelemConnectionTest, GetBadMeminfo) { |
| SetProcDataResponse(kFakeBadMeminfoFileContents); |
| |
| auto mem_total = connection()->GetItem(TelemetryItemEnum::kMemTotalMebibytes, |
| base::TimeDelta()); |
| EXPECT_EQ(mem_total, base::nullopt); |
| auto mem_free = connection()->GetItem(TelemetryItemEnum::kMemFreeMebibytes, |
| base::TimeDelta()); |
| EXPECT_EQ(mem_free, base::nullopt); |
| } |
| |
| // Test that we can retrieve kNumRunnableEntities. |
| TEST_F(TelemConnectionTest, GetRunnableEntities) { |
| SetProcDataResponse(kFakeLoadavgFileContents); |
| |
| auto runnable_entities = connection()->GetItem( |
| TelemetryItemEnum::kNumRunnableEntities, base::TimeDelta()); |
| CheckIntegerItemResponse(runnable_entities, |
| TelemetryItemEnum::kNumRunnableEntities); |
| } |
| |
| // Test that we can retrieve kNumExistingEntities. |
| TEST_F(TelemConnectionTest, GetExistingEntities) { |
| SetProcDataResponse(kFakeLoadavgFileContents); |
| |
| auto existing_entities = connection()->GetItem( |
| TelemetryItemEnum::kNumExistingEntities, base::TimeDelta()); |
| CheckIntegerItemResponse(existing_entities, |
| TelemetryItemEnum::kNumExistingEntities); |
| } |
| |
| // Test that an incorrectly formatted /proc/loadavg will fail to parse. |
| TEST_F(TelemConnectionTest, GetBadLoadAvg) { |
| SetProcDataResponse(kFakeBadLoadavgFileContents); |
| |
| auto runnable_entities = connection()->GetItem( |
| TelemetryItemEnum::kNumRunnableEntities, base::TimeDelta()); |
| EXPECT_EQ(runnable_entities, base::nullopt); |
| auto existing_entities = connection()->GetItem( |
| TelemetryItemEnum::kNumExistingEntities, base::TimeDelta()); |
| EXPECT_EQ(existing_entities, base::nullopt); |
| } |
| |
| // Test that we can retrieve kTotalIdleTime. |
| TEST_F(TelemConnectionTest, GetTotalIdleTime) { |
| SetProcDataResponse(kFakeStatFileContents); |
| |
| auto idle_time = connection()->GetItem( |
| TelemetryItemEnum::kTotalIdleTimeUserHz, base::TimeDelta()); |
| CheckStringItemResponse(idle_time, TelemetryItemEnum::kTotalIdleTimeUserHz); |
| } |
| |
| // Test that we can retrieve kIdleTimePerCPU. |
| TEST_F(TelemConnectionTest, GetIdleTimePerCPU) { |
| SetProcDataResponse(kFakeStatFileContents); |
| |
| auto idle_times = connection()->GetItem( |
| TelemetryItemEnum::kIdleTimePerCPUUserHz, base::TimeDelta()); |
| CheckListItemResponse(idle_times, TelemetryItemEnum::kIdleTimePerCPUUserHz); |
| } |
| |
| // Test that an incorrectly formatted /proc/stat will fail to parse. |
| TEST_F(TelemConnectionTest, GetBadStat) { |
| SetProcDataResponse(kFakeBadStatFileContents); |
| |
| auto total_idle_time = connection()->GetItem( |
| TelemetryItemEnum::kTotalIdleTimeUserHz, base::TimeDelta()); |
| EXPECT_EQ(total_idle_time, base::nullopt); |
| auto idle_times_per_cpu = connection()->GetItem( |
| TelemetryItemEnum::kIdleTimePerCPUUserHz, base::TimeDelta()); |
| EXPECT_EQ(idle_times_per_cpu, base::nullopt); |
| } |
| |
| // Test that we can retrieve a group of telemetry items. |
| TEST_F(TelemConnectionTest, GetDiskGroup) { |
| SetProcDataResponse(kFakeMeminfoFileContents); |
| |
| auto disk_group = |
| connection()->GetGroup(TelemetryGroupEnum::kDisk, base::TimeDelta()); |
| CheckDiskGroupResponse(disk_group); |
| } |
| |
| } // namespace diagnostics |