diff --git a/Package.swift b/Package.swift index c110301e..5cd7e4b4 100644 --- a/Package.swift +++ b/Package.swift @@ -422,6 +422,7 @@ let package = Package( "internal/flags/BUILD", "internal/network/BUILD", "internal/base/BUILD", + "internal/test/BUILD", // tests "connections/listeners_test.cc", "connections/strategy_test.cc", @@ -538,6 +539,10 @@ let package = Package( "internal/network/http_request_test.cc", "internal/network/http_client_impl_test.cc", "internal/network/http_status_code_test.cc", + "internal/test/fake_clock_test.cc", + "internal/test/fake_timer_test.cc", + "internal/test/fake_device_info_test.cc", + "internal/test/fake_task_runner_test.cc", // simulation "connections/implementation/offline_simulation_user.cc", "connections/implementation/simulation_user.cc", diff --git a/internal/platform/BUILD b/internal/platform/BUILD index 6231c4b3..67093db8 100644 --- a/internal/platform/BUILD +++ b/internal/platform/BUILD @@ -354,6 +354,7 @@ cc_library( "//fastpair:__subpackages__", "//internal/flags:__subpackages__", "//internal/platform/implementation/windows:__subpackages__", + "//internal/test:__subpackages__", "//location/nearby/cpp:__subpackages__", "//location/nearby/testing/nearby_native:__subpackages__", "//presence:__subpackages__", diff --git a/internal/platform/implementation/BUILD b/internal/platform/implementation/BUILD index 198ef9ef..5fabe6f4 100644 --- a/internal/platform/implementation/BUILD +++ b/internal/platform/implementation/BUILD @@ -42,6 +42,7 @@ cc_library( "//fastpair:__subpackages__", "//internal/platform:__pkg__", "//internal/platform/implementation:__subpackages__", + "//internal/test:__subpackages__", "//location/nearby/analytics/cpp:__subpackages__", "//presence:__subpackages__", ], diff --git a/internal/platform/implementation/g3/BUILD b/internal/platform/implementation/g3/BUILD index ad8ecd93..63212d3d 100644 --- a/internal/platform/implementation/g3/BUILD +++ b/internal/platform/implementation/g3/BUILD @@ -132,6 +132,7 @@ cc_library( "//internal/network:__subpackages__", "//internal/platform:__subpackages__", "//internal/proto/analytics:__subpackages__", + "//internal/test:__subpackages__", "//location/nearby/cpp:__subpackages__", "//presence:__subpackages__", ], diff --git a/internal/test/BUILD b/internal/test/BUILD new file mode 100644 index 00000000..618ee62f --- /dev/null +++ b/internal/test/BUILD @@ -0,0 +1,56 @@ +licenses(["notice"]) + +cc_library( + name = "test", + srcs = [ + "fake_clock.cc", + "fake_task_runner.cc", + "fake_timer.cc", + ], + hdrs = [ + "fake_clock.h", + "fake_device_info.h", + "fake_task_runner.h", + "fake_timer.h", + ], + copts = [ + "-Ithird_party", + ], + visibility = ["//visibility:public"], + deps = [ + "//internal/base:bluetooth_address", + "//internal/platform:types", + "//internal/platform/implementation:types", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/synchronization", + "@com_google_absl//absl/time", + "@com_google_absl//absl/types:span", # fixdeps: keep + ], +) + +cc_test( + name = "test_test", + size = "small", + timeout = "short", + srcs = [ + "fake_clock_test.cc", + "fake_device_info_test.cc", + "fake_task_runner_test.cc", + "fake_timer_test.cc", + ], + copts = [ + "-Ithird_party", + ], + shard_count = 8, + deps = [ + ":test", + "//internal/platform:types", + "//internal/platform/implementation:types", + "//internal/platform/implementation/g3", + "@com_github_protobuf_matchers//protobuf-matchers", + "@com_google_absl//absl/time", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/internal/test/fake_clock.cc b/internal/test/fake_clock.cc new file mode 100644 index 00000000..418863b6 --- /dev/null +++ b/internal/test/fake_clock.cc @@ -0,0 +1,71 @@ +// Copyright 2021 Google LLC +// +// 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 +// +// https://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 "internal/test/fake_clock.h" + +#include +#include +#include +#include + +namespace nearby { + +absl::Time FakeClock::Now() const { return now_; } + +void FakeClock::AddObserver(absl::string_view name, + std::function observer) { + absl::MutexLock lock(&mutex_); + observers_.emplace(name, std::move(observer)); +} + +void FakeClock::RemoveObserver(absl::string_view name) { + absl::MutexLock lock(&mutex_); + observers_.erase(name); +} + +void FakeClock::FastForward(absl::Duration duration) { + std::vector timer_callback_ids; + now_ += duration; + { + absl::MutexLock lock(&mutex_); + for (const auto& observer : observers_) { + timer_callback_ids.push_back(observer.first); + } + } + + for (const auto& timer_callback_id : timer_callback_ids) { + // Timer may be closed during other timer running, so only run callback + // on available timers. + bool is_alive_timer = true; + std::function callback; + + { + absl::MutexLock lock(&mutex_); + is_alive_timer = observers_.contains(timer_callback_id); + if (!is_alive_timer) { + continue; + } + callback = observers_[timer_callback_id]; + } + + callback(); + } +} + +int FakeClock::GetObserversCount() { + absl::MutexLock lock(&mutex_); + return observers_.size(); +} + +} // namespace nearby diff --git a/internal/test/fake_clock.h b/internal/test/fake_clock.h new file mode 100644 index 00000000..dc64aae0 --- /dev/null +++ b/internal/test/fake_clock.h @@ -0,0 +1,55 @@ +// Copyright 2021 Google LLC +// +// 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 +// +// https://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 THIRD_PARTY_NEARBY_INTERNAL_TEST_FAKE_CLOCK_H_ +#define THIRD_PARTY_NEARBY_INTERNAL_TEST_FAKE_CLOCK_H_ + +#include +#include + +#include "absl/base/thread_annotations.h" +#include "absl/container/flat_hash_map.h" +#include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "internal/platform/clock.h" + +namespace nearby { + +class FakeClock : public Clock { + public: + FakeClock() { now_ = absl::Now(); } + FakeClock(FakeClock&&) = default; + FakeClock& operator=(FakeClock&&) = default; + + absl::Time Now() const override; + + void AddObserver(absl::string_view name, std::function observer) + ABSL_LOCKS_EXCLUDED(mutex_); + void RemoveObserver(absl::string_view name) ABSL_LOCKS_EXCLUDED(mutex_); + + void FastForward(absl::Duration duration); + + int GetObserversCount() ABSL_LOCKS_EXCLUDED(mutex_); + + private: + absl::Time now_; + mutable absl::Mutex mutex_; + absl::flat_hash_map> observers_ + ABSL_GUARDED_BY(mutex_); +}; +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_INTERNAL_TEST_FAKE_CLOCK_H_ diff --git a/internal/test/fake_clock_test.cc b/internal/test/fake_clock_test.cc new file mode 100644 index 00000000..d06966e6 --- /dev/null +++ b/internal/test/fake_clock_test.cc @@ -0,0 +1,53 @@ +// Copyright 2021 Google LLC +// +// 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 +// +// https://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 "internal/test/fake_clock.h" + +#include "gmock/gmock.h" +#include "protobuf-matchers/protocol-buffer-matchers.h" +#include "gtest/gtest.h" + +namespace nearby { +namespace { + +TEST(FakeClock, TestGetCurrentTime) { + FakeClock clock; + EXPECT_GT(absl::ToUnixNanos(clock.Now()), 0); +} + +TEST(FakeClock, TestFastForward) { + FakeClock clock; + absl::Time time1 = clock.Now(); + clock.FastForward(absl::Nanoseconds(1500)); + absl::Time time2 = clock.Now(); + EXPECT_EQ(absl::ToUnixNanos(time1) + 1500, absl::ToUnixNanos(time2)); + EXPECT_EQ(clock.GetObserversCount(), 0); +} + +TEST(FakeClock, TestObserver) { + FakeClock clock; + int count = 0; + auto observer = [&count]() { count++; }; + clock.AddObserver("test", observer); + clock.FastForward(absl::Nanoseconds(1500)); + EXPECT_EQ(count, 1); + clock.FastForward(absl::Nanoseconds(1500)); + EXPECT_EQ(count, 2); + clock.RemoveObserver("test"); + clock.FastForward(absl::Nanoseconds(1500)); + EXPECT_EQ(count, 2); +} + +} // namespace +} // namespace nearby diff --git a/internal/test/fake_device_info.h b/internal/test/fake_device_info.h new file mode 100644 index 00000000..4f296702 --- /dev/null +++ b/internal/test/fake_device_info.h @@ -0,0 +1,179 @@ +// Copyright 2022 Google LLC +// +// 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 +// +// https://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 THIRD_PARTY_NEARBY_INTERNAL_TEST_FAKE_DEVICE_INFO_H_ +#define THIRD_PARTY_NEARBY_INTERNAL_TEST_FAKE_DEVICE_INFO_H_ + +#include +#include +#include +#include +#include +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/strings/string_view.h" +#include "internal/base/bluetooth_address.h" +#include "internal/platform/device_info.h" +#include "internal/platform/implementation/device_info.h" + +namespace nearby { + +class FakeDeviceInfo : public DeviceInfo { + public: + std::u16string GetOsDeviceName() const override { return device_name_; } + + api::DeviceInfo::DeviceType GetDeviceType() const override { + return device_type_; + } + + api::DeviceInfo::OsType GetOsType() const override { return os_type_; } + + std::optional GetFullName() const override { + return full_name_; + } + std::optional GetGivenName() const override { + return given_name_; + } + std::optional GetLastName() const override { + return last_name_; + } + std::optional GetProfileUserName() const override { + return profile_user_name_; + } + + std::filesystem::path GetDownloadPath() const override { + return download_path_; + } + + std::filesystem::path GetAppDataPath() const override { + return app_data_path_; + } + + std::filesystem::path GetTemporaryPath() const override { return temp_path_; } + + std::optional GetAvailableDiskSpaceInBytes( + const std::filesystem::path& path) const override { + std::wstring path_key = path.wstring(); + auto it = available_space_map_.find(path_key); + if (it != available_space_map_.end()) { + return it->second; + } + return std::numeric_limits::max(); + } + + bool IsScreenLocked() const override { return is_screen_locked_; } + + void RegisterScreenLockedListener( + absl::string_view listener_name, + std::function callback) override { + screen_locked_listeners_.emplace(listener_name, callback); + } + + void UnregisterScreenLockedListener( + absl::string_view listener_name) override { + screen_locked_listeners_.erase(listener_name); + } + + int GetScreenLockedListenerCount() { return screen_locked_listeners_.size(); } + + // Mock methods. + void SetOsDeviceName(std::u16string_view device_name) { + device_name_ = device_name; + } + + void SetDeviceType(api::DeviceInfo::DeviceType device_type) { + device_type_ = device_type; + } + + void SetOsType(api::DeviceInfo::OsType os_type) { os_type_ = os_type; } + + void SetFullName(std::optional full_name) { + if (full_name.has_value() && !full_name->empty()) { + full_name_ = full_name; + } else { + full_name_ = std::nullopt; + } + } + + void SetGivenName(std::optional given_name) { + if (given_name.has_value() && !given_name->empty()) { + given_name_ = given_name; + } else { + given_name_ = std::nullopt; + } + } + + void SetLastName(std::optional last_name) { + if (last_name.has_value() && !last_name->empty()) { + last_name_ = last_name; + } else { + last_name_ = std::nullopt; + } + } + + void SetProfileUserName(std::optional profile_user_name) { + if (profile_user_name.has_value() && !profile_user_name->empty()) { + profile_user_name_ = profile_user_name; + } else { + profile_user_name_ = std::nullopt; + } + } + + void SetDownloadPath(std::filesystem::path path) { download_path_ = path; } + + void SetAppDataPath(std::filesystem::path path) { app_data_path_ = path; } + + void SetTemporaryPath(std::filesystem::path path) { temp_path_ = path; } + + void SetAvailableDiskSpaceInBytes(const std::filesystem::path& path, + size_t available_bytes) { + available_space_map_.emplace(path.wstring(), available_bytes); + } + + void ResetDiskSpace() { available_space_map_.clear(); } + + void SetScreenLocked(bool locked) { + is_screen_locked_ = locked; + for (auto& listener : screen_locked_listeners_) { + if (locked) { + listener.second(api::DeviceInfo::ScreenStatus::kLocked); + } else { + listener.second(api::DeviceInfo::ScreenStatus::kUnlocked); + } + } + } + + private: + std::u16string device_name_ = u"nearby"; + api::DeviceInfo::DeviceType device_type_ = + api::DeviceInfo::DeviceType::kLaptop; + api::DeviceInfo::OsType os_type_ = api::DeviceInfo::OsType::kWindows; + std::optional full_name_ = u"Nearby"; + std::optional given_name_ = u"Nearby"; + std::optional last_name_ = u"Nearby"; + std::optional profile_user_name_ = "nearby"; + std::filesystem::path download_path_ = std::filesystem::temp_directory_path(); + std::filesystem::path app_data_path_ = std::filesystem::temp_directory_path(); + std::filesystem::path temp_path_ = std::filesystem::temp_directory_path(); + absl::flat_hash_map available_space_map_; + absl::flat_hash_map> + screen_locked_listeners_; + bool is_screen_locked_ = false; +}; + +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_INTERNAL_TEST_FAKE_DEVICE_INFO_H_ diff --git a/internal/test/fake_device_info_test.cc b/internal/test/fake_device_info_test.cc new file mode 100644 index 00000000..53ffc093 --- /dev/null +++ b/internal/test/fake_device_info_test.cc @@ -0,0 +1,192 @@ +// Copyright 2021 Google LLC +// +// 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 +// +// https://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 "internal/test/fake_device_info.h" + +#include +#include +#include +#include + +#include "gtest/gtest.h" +#include "internal/platform/implementation/device_info.h" + +namespace nearby { +namespace { + +TEST(FakeDeviceInfo, DeviceName) { + FakeDeviceInfo device_info; + device_info.SetOsDeviceName(u"windows"); + EXPECT_EQ(device_info.GetOsDeviceName(), u"windows"); +} + +TEST(FakeDeviceInfo, DeviceType) { + FakeDeviceInfo device_info; + device_info.SetDeviceType(api::DeviceInfo::DeviceType::kPhone); + EXPECT_EQ(device_info.GetDeviceType(), api::DeviceInfo::DeviceType::kPhone); +} + +TEST(FakeDeviceInfo, OsType) { + FakeDeviceInfo device_info; + device_info.SetOsType(api::DeviceInfo::OsType::kWindows); + EXPECT_EQ(device_info.GetOsType(), api::DeviceInfo::OsType::kWindows); +} + +TEST(FakeDeviceInfo, FullName) { + FakeDeviceInfo device_info; + device_info.SetFullName(u"windows"); + EXPECT_EQ(device_info.GetFullName(), u"windows"); + device_info.SetFullName(std::nullopt); + EXPECT_FALSE(device_info.GetFullName().has_value()); +} + +TEST(FakeDeviceInfo, GivenName) { + FakeDeviceInfo device_info; + device_info.SetGivenName(u"windows"); + EXPECT_EQ(device_info.GetGivenName(), u"windows"); + device_info.SetGivenName(std::nullopt); + EXPECT_FALSE(device_info.GetGivenName().has_value()); +} + +TEST(FakeDeviceInfo, LastName) { + FakeDeviceInfo device_info; + device_info.SetLastName(u"windows"); + EXPECT_EQ(device_info.GetLastName(), u"windows"); + device_info.SetLastName(std::nullopt); + EXPECT_FALSE(device_info.GetLastName().has_value()); +} + +TEST(FakeDeviceInfo, ProfileUserName) { + FakeDeviceInfo device_info; + device_info.SetProfileUserName("windows"); + EXPECT_EQ(device_info.GetProfileUserName(), "windows"); + device_info.SetProfileUserName(std::nullopt); + EXPECT_FALSE(device_info.GetProfileUserName().has_value()); +} + +TEST(FakeDeviceInfo, GetDownloadPath) { + FakeDeviceInfo device_info; + EXPECT_EQ(device_info.GetDownloadPath(), + std::filesystem::temp_directory_path()); + device_info.SetDownloadPath(std::filesystem::temp_directory_path() / "test"); + EXPECT_EQ(device_info.GetDownloadPath(), + std::filesystem::temp_directory_path() / "test"); +} + +TEST(FakeDeviceInfo, GetAppDataPath) { + FakeDeviceInfo device_info; + EXPECT_EQ(device_info.GetAppDataPath(), + std::filesystem::temp_directory_path()); + device_info.SetAppDataPath(std::filesystem::temp_directory_path() / "test"); + EXPECT_EQ(device_info.GetAppDataPath(), + std::filesystem::temp_directory_path() / "test"); +} + +TEST(FakeDeviceInfo, GetTemporaryPath) { + FakeDeviceInfo device_info; + EXPECT_EQ(device_info.GetTemporaryPath(), + std::filesystem::temp_directory_path()); + device_info.SetTemporaryPath(std::filesystem::temp_directory_path() / "test"); + EXPECT_EQ(device_info.GetTemporaryPath(), + std::filesystem::temp_directory_path() / "test"); +} + +TEST(FakeDeviceInfo, GetAvailableDiskSpaceInBytes) { + FakeDeviceInfo device_info; + device_info.SetDownloadPath("download"); + device_info.SetAppDataPath("appdata"); + device_info.SetTemporaryPath("temp"); + + device_info.SetAvailableDiskSpaceInBytes(device_info.GetDownloadPath(), 10); + device_info.SetAvailableDiskSpaceInBytes(device_info.GetAppDataPath(), 100); + device_info.SetAvailableDiskSpaceInBytes(device_info.GetTemporaryPath(), + 1000); + + EXPECT_EQ( + device_info.GetAvailableDiskSpaceInBytes(device_info.GetDownloadPath()), + 10); + EXPECT_EQ( + device_info.GetAvailableDiskSpaceInBytes(device_info.GetAppDataPath()), + 100); + EXPECT_EQ( + device_info.GetAvailableDiskSpaceInBytes(device_info.GetTemporaryPath()), + 1000); +} + +TEST(FakeDeviceInfo, RegisterScreenLockedListener) { + std::function listener_1 = + [](api::DeviceInfo::ScreenStatus) {}; + std::function listener_2 = + [](api::DeviceInfo::ScreenStatus) {}; + + FakeDeviceInfo device_info; + EXPECT_EQ(device_info.GetScreenLockedListenerCount(), 0); + + device_info.RegisterScreenLockedListener("listener_1", listener_1); + EXPECT_EQ(device_info.GetScreenLockedListenerCount(), 1); + + device_info.RegisterScreenLockedListener("listener_2", listener_2); + EXPECT_EQ(device_info.GetScreenLockedListenerCount(), 2); +} + +TEST(FakeDeviceInfo, UnregisterScreenLockedListener) { + std::function listener_1 = + [](api::DeviceInfo::ScreenStatus) {}; + std::function listener_2 = + [](api::DeviceInfo::ScreenStatus) {}; + + FakeDeviceInfo device_info; + EXPECT_EQ(device_info.GetScreenLockedListenerCount(), 0); + + device_info.RegisterScreenLockedListener("listener_1", listener_1); + device_info.RegisterScreenLockedListener("listener_2", listener_2); + EXPECT_EQ(device_info.GetScreenLockedListenerCount(), 2); + + device_info.UnregisterScreenLockedListener("listener_1"); + EXPECT_EQ(device_info.GetScreenLockedListenerCount(), 1); + + device_info.UnregisterScreenLockedListener("listener_2"); + EXPECT_EQ(device_info.GetScreenLockedListenerCount(), 0); +} + +TEST(FakeDeviceInfo, UpdateScreenLockedListener) { + api::DeviceInfo::ScreenStatus screen_locked_tracker_1 = + api::DeviceInfo::ScreenStatus::kUndetermined; + api::DeviceInfo::ScreenStatus screen_locked_tracker_2 = + api::DeviceInfo::ScreenStatus::kUndetermined; + + std::function listener_1 = + [&screen_locked_tracker_1](api::DeviceInfo::ScreenStatus status) { + screen_locked_tracker_1 = status; + }; + std::function listener_2 = + [&screen_locked_tracker_2](api::DeviceInfo::ScreenStatus status) { + screen_locked_tracker_2 = status; + }; + + FakeDeviceInfo device_info; + device_info.RegisterScreenLockedListener("listener_1", listener_1); + device_info.RegisterScreenLockedListener("listener_2", listener_2); + + device_info.SetScreenLocked(true); + EXPECT_EQ(screen_locked_tracker_1, api::DeviceInfo::ScreenStatus::kLocked); + EXPECT_EQ(screen_locked_tracker_2, api::DeviceInfo::ScreenStatus::kLocked); + + device_info.SetScreenLocked(false); + EXPECT_EQ(screen_locked_tracker_1, api::DeviceInfo::ScreenStatus::kUnlocked); + EXPECT_EQ(screen_locked_tracker_2, api::DeviceInfo::ScreenStatus::kUnlocked); +} + +} // namespace +} // namespace nearby diff --git a/internal/test/fake_task_runner.cc b/internal/test/fake_task_runner.cc new file mode 100644 index 00000000..316e74cf --- /dev/null +++ b/internal/test/fake_task_runner.cc @@ -0,0 +1,123 @@ +// Copyright 2022 Google LLC +// +// 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 +// +// https://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 "internal/test/fake_task_runner.h" + +#include +#include // NOLINT +#include +#include +#include + +#include "absl/time/time.h" + +namespace nearby { + +std::atomic_uint FakeTaskRunner::running_thread_count_ = 0; + +bool FakeTaskRunner::PostTask(absl::AnyInvocable task) { + if (mode_ == Mode::kNoPending) { + run(std::move(task)); + return true; + } + pending_tasks_.push_back(std::move(task)); + return true; +} + +bool FakeTaskRunner::PostDelayedTask(absl::Duration delay, + absl::AnyInvocable task) { + std::unique_ptr timer = std::make_unique(clock_); + Timer* timer_ptr = timer.get(); + uint32_t id = GenerateId(); + pending_delayed_tasks_.emplace(id, std::move(timer)); + timer_ptr->Start(delay / absl::Milliseconds(1), 0, + [this, task = std::move(task), id]() mutable { + PostTask(std::move(task)); + completed_delayed_tasks_.push_back(id); + }); + return true; +} + +void FakeTaskRunner::RunNextTask() { + if (pending_tasks_.empty()) { + return; + } + + run(std::move(pending_tasks_.front())); + pending_tasks_.erase(pending_tasks_.begin()); +} + +void FakeTaskRunner::RunAllPendingTasks() { + while (!pending_tasks_.empty()) { + RunNextTask(); + } +} + +const std::vector>& FakeTaskRunner::GetPendingTasks() + const { + return pending_tasks_; +} + +const absl::flat_hash_map>& +FakeTaskRunner::GetPendingDelayedTask() { + if (!completed_delayed_tasks_.empty()) { + for (uint32_t id : completed_delayed_tasks_) { + pending_delayed_tasks_.erase(id); + } + } + return pending_delayed_tasks_; +} + +bool FakeTaskRunner::WaitForRunningTasksWithTimeout(absl::Duration timeout) { + int i = (timeout / absl::Milliseconds(1)) / 50; + while (running_thread_count_ != 0 && i > 0) { + absl::SleepFor(absl::Milliseconds(50)); + --i; + } + + return running_thread_count_ == 0; +} + +uint32_t FakeTaskRunner::GenerateId() { + ++current_id_; + return current_id_; +} + +void FakeTaskRunner::CleanThreads() { + auto it = threads_.begin(); + while (it != threads_.end()) { + // Delete the thread if it is ready + auto status = it->wait_for(std::chrono::seconds(0)); + if (status == std::future_status::ready) { + it = threads_.erase(it); + } else { + ++it; + } + } +} + +void FakeTaskRunner::run(absl::AnyInvocable task) { + absl::MutexLock lock(&mutex_); + CleanThreads(); + ++running_thread_count_; + // Run the task in a new thread, to simulate the real environment. + std::future thread = + std::async(std::launch::async, [&, task = std::move(task)]() mutable { + task(); + --running_thread_count_; + }); + threads_.push_back(std::move(thread)); +} + +} // namespace nearby diff --git a/internal/test/fake_task_runner.h b/internal/test/fake_task_runner.h new file mode 100644 index 00000000..4b433e83 --- /dev/null +++ b/internal/test/fake_task_runner.h @@ -0,0 +1,87 @@ +// Copyright 2022 Google LLC +// +// 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 +// +// https://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 THIRD_PARTY_NEARBY_INTERNAL_TEST_FAKE_TASK_RUNNER_H_ +#define THIRD_PARTY_NEARBY_INTERNAL_TEST_FAKE_TASK_RUNNER_H_ + +#include +#include +#include //NOLINT +#include +#include //NOLINT +#include +#include + +#include "absl/base/thread_annotations.h" +#include "absl/container/flat_hash_map.h" +#include "absl/time/time.h" +#include "internal/platform/task_runner.h" +#include "internal/test/fake_clock.h" +#include "internal/test/fake_timer.h" + +namespace nearby { + +class FakeTaskRunner : public TaskRunner { + public: + enum class Mode { kNoPending, kPending }; + + FakeTaskRunner(FakeClock* clock, uint32_t count) + : clock_(clock), count_(count) {} + ~FakeTaskRunner() override = default; + + bool PostTask(absl::AnyInvocable task) override; + + // No matter the mode is pending or not, always put the task in timer control. + // Caller can move forward time to trigger it. + bool PostDelayedTask(absl::Duration delay, + absl::AnyInvocable task) override; + + // Mocked methods. + void SetMode(Mode mode) { mode_ = mode; } + Mode GetMode() const { return mode_; } + + void RunNextTask(); + void RunAllPendingTasks(); + + const std::vector>& GetPendingTasks() const; + const absl::flat_hash_map>& + GetPendingDelayedTask(); + + int GetConcurrentCount() const { return count_; } + + // In some testcases, we needs to make sure all running tasks completion + // before go to next task. This method can be used for the purpose. + static bool WaitForRunningTasksWithTimeout(absl::Duration timeout); + + private: + uint32_t GenerateId(); + void CleanThreads() ABSL_SHARED_LOCKS_REQUIRED(mutex_); + void run(absl::AnyInvocable task) ABSL_LOCKS_EXCLUDED(mutex_); + + Mode mode_ = Mode::kNoPending; + std::atomic_uint current_id_ = 0; + FakeClock* clock_ = nullptr; + uint32_t count_; + std::vector> pending_tasks_; + std::vector completed_delayed_tasks_; + absl::flat_hash_map> pending_delayed_tasks_; + absl::Mutex mutex_; + std::vector> threads_ ABSL_GUARDED_BY(mutex_); + + static std::atomic_uint running_thread_count_; +}; + +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_INTERNAL_TEST_FAKE_TASK_RUNNER_H_ diff --git a/internal/test/fake_task_runner_test.cc b/internal/test/fake_task_runner_test.cc new file mode 100644 index 00000000..fd2b0554 --- /dev/null +++ b/internal/test/fake_task_runner_test.cc @@ -0,0 +1,115 @@ +// Copyright 2022 Google LLC +// +// 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 +// +// https://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 "internal/test/fake_task_runner.h" + +#include "gtest/gtest.h" +#include "absl/time/time.h" +#include "internal/test/fake_clock.h" + +namespace nearby { +namespace { + +TEST(FakeTaskRunner, PostTask) { + FakeClock clock; + int count = 0; + FakeTaskRunner task_runner{&clock, 1}; + task_runner.PostTask([&count] { ++count; }); + ASSERT_TRUE( + FakeTaskRunner::WaitForRunningTasksWithTimeout(absl::Milliseconds(100))); + EXPECT_EQ(task_runner.GetPendingTasks().size(), 0); + EXPECT_EQ(count, 1); +} + +TEST(FakeTaskRunner, PostDelayedTask) { + FakeClock clock; + int count = 0; + FakeTaskRunner task_runner{&clock, 1}; + task_runner.PostDelayedTask(absl::Seconds(10), [&count] { ++count; }); + EXPECT_EQ(task_runner.GetPendingTasks().size(), 0); + EXPECT_EQ(task_runner.GetPendingDelayedTask().size(), 1); + EXPECT_EQ(count, 0); + clock.FastForward(absl::Seconds(10)); + ASSERT_TRUE( + FakeTaskRunner::WaitForRunningTasksWithTimeout(absl::Milliseconds(100))); + EXPECT_EQ(count, 1); + EXPECT_EQ(task_runner.GetPendingDelayedTask().size(), 0); +} + +TEST(FakeTaskRunner, PostTasksInPendingMode) { + FakeClock clock; + FakeTaskRunner task_runner{&clock, 1}; + task_runner.SetMode(FakeTaskRunner::Mode::kPending); + EXPECT_EQ(task_runner.GetConcurrentCount(), 1); + task_runner.PostTask([]() {}); + task_runner.PostTask([]() {}); + EXPECT_EQ(task_runner.GetPendingTasks().size(), 2); + task_runner.RunNextTask(); + EXPECT_EQ(task_runner.GetPendingTasks().size(), 1); + task_runner.RunNextTask(); + EXPECT_EQ(task_runner.GetPendingTasks().size(), 0); +} + +TEST(FakeTaskRunner, RunAllPostedTasksInPendingMode) { + FakeClock clock; + FakeTaskRunner task_runner{&clock, 1}; + task_runner.SetMode(FakeTaskRunner::Mode::kPending); + task_runner.PostTask([]() {}); + task_runner.PostTask([]() {}); + EXPECT_EQ(task_runner.GetPendingTasks().size(), 2); + task_runner.RunAllPendingTasks(); + ASSERT_TRUE( + FakeTaskRunner::WaitForRunningTasksWithTimeout(absl::Milliseconds(100))); + EXPECT_EQ(task_runner.GetPendingTasks().size(), 0); +} + +TEST(FakeTaskRunner, PostDelayedTaskInPendingMode) { + FakeClock clock; + bool called = false; + FakeTaskRunner task_runner{&clock, 1}; + task_runner.SetMode(FakeTaskRunner::Mode::kPending); + task_runner.PostDelayedTask(absl::Seconds(1), [&called]() { called = true; }); + EXPECT_EQ(task_runner.GetPendingDelayedTask().size(), 1); + clock.FastForward(absl::Seconds(1)); + EXPECT_EQ(task_runner.GetPendingDelayedTask().size(), 0); + EXPECT_EQ(task_runner.GetPendingTasks().size(), 1); + task_runner.RunAllPendingTasks(); + ASSERT_TRUE( + FakeTaskRunner::WaitForRunningTasksWithTimeout(absl::Milliseconds(100))); + EXPECT_EQ(task_runner.GetPendingTasks().size(), 0); + EXPECT_TRUE(called); +} + +TEST(FakeTaskRunner, PostDelayedTaskInDelayedTask) { + FakeClock clock; + int called_count = 0; + FakeTaskRunner task_runner{&clock, 1}; + task_runner.PostDelayedTask( + absl::Seconds(1), [&called_count, &task_runner]() { + ++called_count; + task_runner.PostDelayedTask(absl::Seconds(1), + [&called_count]() { ++called_count; }); + }); + clock.FastForward(absl::Seconds(1)); + ASSERT_TRUE( + FakeTaskRunner::WaitForRunningTasksWithTimeout(absl::Milliseconds(100))); + EXPECT_EQ(called_count, 1); + clock.FastForward(absl::Seconds(1)); + ASSERT_TRUE( + FakeTaskRunner::WaitForRunningTasksWithTimeout(absl::Milliseconds(100))); + EXPECT_EQ(called_count, 2); +} + +} // namespace +} // namespace nearby diff --git a/internal/test/fake_timer.cc b/internal/test/fake_timer.cc new file mode 100644 index 00000000..afc9a86b --- /dev/null +++ b/internal/test/fake_timer.cc @@ -0,0 +1,93 @@ +// Copyright 2021 Google LLC +// +// 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 +// +// https://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 "internal/test/fake_timer.h" + +#include +#include // NOLINT +#include +#include // NOLINT +#include + +#include "absl/time/clock.h" +#include "absl/time/time.h" + +namespace nearby { + +FakeTimer::FakeTimer(FakeClock* clock) : clock_(clock) { + id_ = std::to_string(absl::ToUnixNanos(absl::Now())); + clock_->AddObserver(id_, [this]() { ClockUpdated(); }); +} + +FakeTimer::~FakeTimer() { clock_->RemoveObserver(id_); } + +bool FakeTimer::Start(int delay, int period, + absl::AnyInvocable callback) { + if (is_started_ || delay < 0 || period < 0) { + return false; + } + delay_ = delay; + period_ = period; + callback_ = std::move(callback); + start_time_ = clock_->Now(); + fired_count_ = 0; + is_started_ = true; + + return true; +} + +bool FakeTimer::Stop() { + is_started_ = false; + return true; +} + +bool FakeTimer::IsRunning() { return is_started_; } + +void FakeTimer::ClockUpdated() { + if (!is_started_) { + return; + } + + absl::Time now = clock_->Now(); + int64_t duration = absl::ToInt64Milliseconds(now - start_time_); + FakeClock* clock = clock_; + + if (duration >= delay_ && fired_count_ == 0) { + ++fired_count_; + callback_(); + } + + if (period_ == 0 || duration < delay_ || + ((clock_ != nullptr) && (clock_ != clock))) { + return; + } + + int count = (duration - delay_) / period_; + int should_fire_count = count - fired_count_ + 1; + for (int i = 0; i < should_fire_count; ++i) { + ++fired_count_; + callback_(); + } +} + +bool FakeTimer::FireNow() { + if (IsRunning() && callback_) { + callback_(); + return true; + } + + return false; +} + +} // namespace nearby diff --git a/internal/test/fake_timer.h b/internal/test/fake_timer.h new file mode 100644 index 00000000..e803f4af --- /dev/null +++ b/internal/test/fake_timer.h @@ -0,0 +1,52 @@ +// Copyright 2021 Google LLC +// +// 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 +// +// https://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 THIRD_PARTY_NEARBY_INTERNAL_TEST_FAKE_TIMER_H_ +#define THIRD_PARTY_NEARBY_INTERNAL_TEST_FAKE_TIMER_H_ + +#include + +#include "internal/platform/timer.h" +#include "internal/test/fake_clock.h" + +namespace nearby { +class FakeTimer : public Timer { + public: + explicit FakeTimer(FakeClock* clock); + ~FakeTimer() override; + FakeTimer(FakeTimer&&) = default; + FakeTimer& operator=(FakeTimer&&) = default; + + bool Start(int delay, int period, + absl::AnyInvocable callback) override; + bool Stop() override; + bool IsRunning() override; + bool FireNow() override; + + private: + void ClockUpdated(); + + std::string id_; + int delay_ = 0; + int period_ = false; + int fired_count_ = 0; + absl::Time start_time_; + bool is_started_ = false; + absl::AnyInvocable callback_; + FakeClock* clock_ = nullptr; +}; + +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_INTERNAL_TEST_FAKE_TIMER_H_ diff --git a/internal/test/fake_timer_test.cc b/internal/test/fake_timer_test.cc new file mode 100644 index 00000000..2bbb59b4 --- /dev/null +++ b/internal/test/fake_timer_test.cc @@ -0,0 +1,164 @@ +// Copyright 2021 Google LLC +// +// 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 +// +// https://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 "internal/test/fake_timer.h" + +#include "gmock/gmock.h" +#include "protobuf-matchers/protocol-buffer-matchers.h" +#include "gtest/gtest.h" +#include "absl/time/time.h" + +namespace nearby { +namespace { + +TEST(FakeTimer, TestOneTimeTimer) { + FakeClock clock; + FakeTimer timer(&clock); + int count = 0; + auto callback = [&count]() { ++count; }; + timer.Start(100, 0, callback); + EXPECT_TRUE(timer.IsRunning()); + clock.FastForward(absl::Milliseconds(1000)); + EXPECT_EQ(count, 1); + EXPECT_TRUE(timer.Stop()); + EXPECT_FALSE(timer.IsRunning()); +} + +TEST(FakeTimer, TestRepeatTimer) { + FakeClock clock; + FakeTimer timer(&clock); + int count = 0; + auto callback = [&count]() { ++count; }; + timer.Start(100, 100, callback); + EXPECT_TRUE(timer.IsRunning()); + clock.FastForward(absl::Milliseconds(1000)); + EXPECT_EQ(count, 10); + EXPECT_TRUE(timer.Stop()); + EXPECT_FALSE(timer.IsRunning()); +} + +TEST(FakeTimer, TestInvalidInput) { + FakeClock clock; + FakeTimer timer(&clock); + int count = 0; + auto callback = [&count]() { ++count; }; + timer.Start(-100, 100, callback); + EXPECT_FALSE(timer.IsRunning()); + EXPECT_TRUE(timer.Stop()); +} + +TEST(FakeTimer, TestStopTimerBeforeClockUpdate) { + FakeClock clock; + FakeTimer timer(&clock); + int count = 0; + auto callback = [&count]() { ++count; }; + timer.Start(100, 100, callback); + EXPECT_TRUE(timer.IsRunning()); + EXPECT_TRUE(timer.Stop()); + EXPECT_FALSE(timer.IsRunning()); + clock.FastForward(absl::Milliseconds(1000)); + EXPECT_EQ(count, 0); +} + +TEST(FakeTimer, TestUpdateMultipleTimesClockForOnetimeTimer) { + FakeClock clock; + FakeTimer timer(&clock); + int count = 0; + auto callback = [&count]() { ++count; }; + timer.Start(100, 0, callback); + EXPECT_TRUE(timer.IsRunning()); + clock.FastForward(absl::Nanoseconds(50)); + EXPECT_EQ(count, 0); + clock.FastForward(absl::Milliseconds(1000)); + EXPECT_EQ(count, 1); + clock.FastForward(absl::Milliseconds(1000)); + EXPECT_EQ(count, 1); + EXPECT_TRUE(timer.Stop()); + EXPECT_FALSE(timer.IsRunning()); +} + +TEST(FakeTimer, TestInstantRunTimer) { + FakeClock clock; + FakeTimer timer(&clock); + int count = 0; + auto callback = [&count]() { ++count; }; + timer.Start(0, 100, callback); + EXPECT_TRUE(timer.IsRunning()); + clock.FastForward(absl::Milliseconds(1000)); + EXPECT_EQ(count, 11); + EXPECT_TRUE(timer.Stop()); + EXPECT_FALSE(timer.IsRunning()); +} + +TEST(FakeTimer, TestTimerDestructor) { + FakeClock clock; + { + FakeTimer timer(&clock); + EXPECT_EQ(clock.GetObserversCount(), 1); + int count = 0; + auto callback = [&count]() { ++count; }; + timer.Start(100, 0, callback); + EXPECT_TRUE(timer.IsRunning()); + EXPECT_EQ(count, 0); + EXPECT_TRUE(timer.Stop()); + EXPECT_FALSE(timer.IsRunning()); + } + EXPECT_EQ(clock.GetObserversCount(), 0); +} + +TEST(FakeTimer, TestTimerFireNow) { + FakeClock clock; + int count = 0; + FakeTimer timer(&clock); + auto callback = [&count]() { ++count; }; + timer.Start(200, 100, callback); + timer.FireNow(); + timer.Stop(); + EXPECT_EQ(count, 1); +} + +TEST(FakeTimer, CloseTimerInTimerProc) { + int count = 0; + FakeClock clock; + FakeTimer timer1(&clock); + FakeTimer timer2(&clock); + timer1.Start(100, 0, [&]() { ++count; }); + timer2.Start(50, 0, [&]() { + ++count; + timer1.Stop(); + }); + + clock.FastForward(absl::Milliseconds(50)); + EXPECT_EQ(count, 1); + clock.FastForward(absl::Milliseconds(50)); + EXPECT_EQ(count, 1); +} + +TEST(FakeTimer, StartTimerInTimerProc) { + int count = 0; + FakeClock clock; + FakeTimer timer1(&clock); + timer1.Start(1000, 0, [&]() { + ++count; + timer1.Stop(); + timer1.Start(1000, 0, [&]() { ++count; }); + }); + clock.FastForward(absl::Milliseconds(1000)); + EXPECT_EQ(count, 1); + clock.FastForward(absl::Milliseconds(1000)); + EXPECT_EQ(count, 2); +} + +} // namespace +} // namespace nearby