| // Copyright 2025 The Fuchsia 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 <lib/async/default.h> |
| #include <lib/driver/power/cpp/wake-lease.h> |
| #include <lib/zx/clock.h> |
| #include <lib/zx/eventpair.h> |
| #include <lib/zx/time.h> |
| #include <zircon/errors.h> |
| #include <zircon/rights.h> |
| #include <zircon/syscalls/object.h> |
| #include <zircon/time.h> |
| |
| #include <cstddef> |
| |
| #include <fbl/ref_ptr.h> |
| #include <gtest/gtest.h> |
| #include <src/lib/testing/loop_fixture/real_loop_fixture.h> |
| #include <src/storage/lib/vfs/cpp/service.h> |
| #include <src/storage/lib/vfs/cpp/synchronous_vfs.h> |
| |
| #include "testing-common.h" |
| |
| namespace power_lib_test { |
| |
| class WakeLeaseTest : public gtest::RealLoopFixture {}; |
| |
| namespace { |
| // Prepares the resources needed to run the fake SAG server. |
| void PrepFakeSag( |
| fbl::RefPtr<fs::Service>& sag, |
| std::shared_ptr<fidl::ServerBindingGroup<fuchsia_power_system::ActivityGovernor>>& bindings, |
| async::Loop& loop, std::shared_ptr<SystemActivityGovernor>& sag_server) { |
| zx::event exec_opportunistic, wake_assertive; |
| zx::event::create(0, &exec_opportunistic); |
| zx::event::create(0, &wake_assertive); |
| sag_server = std::make_shared<SystemActivityGovernor>( |
| std::move(exec_opportunistic), std::move(wake_assertive), loop.dispatcher()); |
| |
| bindings = std::make_shared<fidl::ServerBindingGroup<fuchsia_power_system::ActivityGovernor>>(); |
| sag = fbl::MakeRefCounted<fs::Service>( |
| [&](fidl::ServerEnd<fuchsia_power_system::ActivityGovernor> chan) { |
| bindings->AddBinding(loop.dispatcher(), std::move(chan), sag_server.get(), |
| fidl::kIgnoreBindingClosure); |
| return ZX_OK; |
| }); |
| } |
| |
| // Waits for the ManualWakeLease to observe a suspension signal, then runs |
| // `do_after_suspend`. |
| void DoOperationAfterSuspend( |
| const std::shared_ptr<fdf_power::ManualWakeLease>& op, async::Loop& loop, |
| const std::function<void(const std::shared_ptr<fdf_power::ManualWakeLease>&, async::Loop&)>& |
| do_after_suspend) { |
| if (!op->IsSuspended()) { |
| async::PostDelayedTask( |
| loop.dispatcher(), |
| [op, &loop, do_after_suspend]() { DoOperationAfterSuspend(op, loop, do_after_suspend); }, |
| zx::msec(100)); |
| return; |
| }; |
| |
| do_after_suspend(op, loop); |
| } |
| |
| // Call |op->Start()| once |op| reports it considers the system resumed. |
| // After that, instruct |sag| to suspend. Concurrently call |
| // |DoOperationAfterSuspend|, passing |do_after_suspended| which means that |
| // |do_after_suspended| runs once |op| reports it considers the system |
| // suspended. |
| void StartOperationWhenResumedThenSuspend( |
| const std::shared_ptr<fdf_power::ManualWakeLease>& op, async::Loop& client_loop, |
| const std::shared_ptr<SystemActivityGovernor>& sag, async::Loop& server_loop, |
| const std::function<void(std::shared_ptr<fdf_power::ManualWakeLease>, async::Loop&)>& |
| do_after_suspended) { |
| if (op->IsSuspended()) { |
| async::PostDelayedTask( |
| client_loop.dispatcher(), |
| [op, &client_loop, sag, &server_loop, do_after_suspended]() { |
| StartOperationWhenResumedThenSuspend(op, client_loop, sag, server_loop, |
| do_after_suspended); |
| }, |
| zx::msec(100)); |
| return; |
| } |
| |
| EXPECT_TRUE(op->Start()); |
| EXPECT_TRUE(op->GetWakeLeaseCopy().is_error()); |
| async::PostTask(server_loop.dispatcher(), [sag]() { sag->SendBeforeSuspend(); }); |
| DoOperationAfterSuspend(op, client_loop, do_after_suspended); |
| } |
| |
| void CheckLeaseAcquired(const std::shared_ptr<fdf_power::ManualWakeLease>& op, async::Loop& loop) { |
| EXPECT_TRUE(op->End().is_ok()); |
| loop.Quit(); |
| } |
| |
| } // namespace |
| |
| // Run a `TimeoutWakeLease` test with a fake SAG where the fake SAG and the |
| // client run on their own threads. This creates a `TimeoutWakeLease` which is |
| // connected to the fake SAG. Once the fake SAG and lease are created, |
| // `test_operations` and `sag_operations` functions are called concurrently |
| // on the appropriate dispatcher. `test_operations` has access to the lease, |
| // fake_sag and their respective dispatchers so that it can run the test logic. |
| // |
| // It is expected that `test_operations` will quit the `client_loop` before |
| // it completes because the test infrastructure code waits on this loop before |
| // terminating. |
| template <typename WakeLease> |
| void DoWakeLeaseTest( |
| const std::function<void(std::shared_ptr<WakeLease>, async::Loop&, |
| std::shared_ptr<SystemActivityGovernor>&, async::Loop&)>& |
| test_operations, |
| const std::function<void(std::shared_ptr<SystemActivityGovernor>)>& sag_operations) { |
| async::Loop server_loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| server_loop.StartThread("server-loop"); |
| |
| async::Loop client_loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| client_loop.StartThread("client-loop"); |
| |
| // The server needs to outlive the client, so create references that exist |
| // until after the client's work concludes. Later we'll make sure the shared |
| // pointers are destroyed on teh server thread. |
| std::shared_ptr<SystemActivityGovernor> sag_server; |
| std::shared_ptr<fidl::ServerBindingGroup<fuchsia_power_system::ActivityGovernor>> bindings; |
| fbl::RefPtr<fs::Service> sag; |
| |
| // Create a channel connected to client and server. |
| fidl::Endpoints<fuchsia_power_system::ActivityGovernor> sag_endpoints = |
| fidl::Endpoints<fuchsia_power_system::ActivityGovernor>::Create(); |
| |
| async::PostTask( |
| server_loop.dispatcher(), [&client_loop, &server_loop, &sag_server, &bindings, &sag, |
| &sag_endpoints, test_operations, sag_operations]() mutable { |
| // First create SAG and related entities. |
| PrepFakeSag(sag, bindings, server_loop, sag_server); |
| |
| sag->ConnectService(sag_endpoints.server.TakeChannel()); |
| |
| // Extract the channel from the client end, because passing a ClientEnd to |
| // another thread causes problems with thread unsafe FIDL bindings. |
| zx::channel client = sag_endpoints.client.TakeChannel(); |
| |
| // Now that we've initialized the server, initialize the client side. |
| async::PostTask( |
| client_loop.dispatcher(), [&client_loop, &server_loop, client = std::move(client), |
| &sag_server, test_operations, sag_operations]() mutable { |
| // Create the wake lease on the client's thread so the client |
| // because the FIDL bindings are not threadsafe. |
| std::shared_ptr<WakeLease> op = std::make_shared<WakeLease>( |
| client_loop.dispatcher(), "test-operation", |
| fidl::ClientEnd<fuchsia_power_system::ActivityGovernor>(std::move(client))); |
| |
| // Run whatever the test wants us to run on the server's thread. |
| async::PostTask(server_loop.dispatcher(), |
| [&sag_server, sag_operations]() { sag_operations(sag_server); }); |
| |
| // Run the function provided by the test code. |
| async::PostTask(client_loop.dispatcher(), |
| [op, &server_loop, &client_loop, test_operations, &sag_server]() { |
| test_operations(op, client_loop, sag_server, server_loop); |
| }); |
| }); |
| }); |
| |
| // The client will quit its loop after doing its work, so wait for it. |
| client_loop.JoinThreads(); |
| |
| // On the server thread, destroy the server objects. |
| async::PostTask(server_loop.dispatcher(), [&sag_server, &bindings, &sag, &server_loop]() { |
| // Destroy all the shared objects on the thread where they were created. |
| sag.reset(); |
| bindings.reset(); |
| sag_server.reset(); |
| server_loop.Quit(); |
| }); |
| |
| // Shut down the server thread. |
| server_loop.JoinThreads(); |
| } |
| |
| // Create an ManualWakeLease and allow it to observe a resume signal. Then |
| // check that no actual lease is taken. |
| TEST_F(WakeLeaseTest, TestManualWakeLeaseWhenResumed) { |
| const std::function<void(const std::shared_ptr<fdf_power::ManualWakeLease>, async::Loop&, |
| const std::shared_ptr<SystemActivityGovernor>&, async::Loop&)> |
| test_func = |
| [&test_func](const std::shared_ptr<fdf_power::ManualWakeLease> op, async::Loop& loop, |
| const std::shared_ptr<SystemActivityGovernor>& sag, async::Loop& sag_loop) { |
| if (op->IsSuspended()) { |
| async::PostDelayedTask( |
| loop.dispatcher(), |
| [op, &loop, sag, &sag_loop, &test_func]() { test_func(op, loop, sag, sag_loop); }, |
| zx::msec(100)); |
| return; |
| } |
| |
| EXPECT_FALSE(op->GetWakeLeaseCopy().is_ok()); |
| loop.Quit(); |
| }; |
| |
| DoWakeLeaseTest<fdf_power::ManualWakeLease>( |
| test_func, |
| [](const std::shared_ptr<SystemActivityGovernor>& sag) { sag->SendAfterResume(); }); |
| } |
| |
| // After the ManualWakeLease is created, have it observe a resume and then |
| // verify it works as expected when the operation starts and ends. |
| TEST_F(WakeLeaseTest, TestManualWakeLeaseStartAndEndAfterResumeIsObserved) { |
| const std::function<void(const std::shared_ptr<fdf_power::ManualWakeLease>, async::Loop&, |
| const std::shared_ptr<SystemActivityGovernor>&, async::Loop&)> |
| test_func = |
| [&test_func](const std::shared_ptr<fdf_power::ManualWakeLease> op, async::Loop& loop, |
| const std::shared_ptr<SystemActivityGovernor>& sag, async::Loop& sag_loop) { |
| // Wait for us to be in a resumed state so the atomic op obesrved the |
| // system state change |
| if (op->IsSuspended()) { |
| async::PostDelayedTask( |
| loop.dispatcher(), |
| [op, &loop, sag, &sag_loop, &test_func]() { test_func(op, loop, sag, sag_loop); }, |
| zx::msec(100)); |
| return; |
| } |
| |
| // Since the system is resumed we expect no lease to be taken |
| EXPECT_TRUE(op->Start()); |
| EXPECT_TRUE(op->GetWakeLeaseCopy().is_error()); |
| // Since the system was resumed teh whole time, there should be no |
| // lease to return when the operation ends. |
| EXPECT_TRUE(op->End().is_error()); |
| loop.Quit(); |
| }; |
| |
| DoWakeLeaseTest<fdf_power::ManualWakeLease>( |
| test_func, |
| [](const std::shared_ptr<SystemActivityGovernor>& sag) { sag->SendAfterResume(); }); |
| } |
| |
| // Test ManualWakeLease when it starts while the system is suspended. Also |
| // check that duplicate `Start` calls result in taking only one lease. |
| TEST_F(WakeLeaseTest, TestManualWakeLeaseWhenSuspended) { |
| std::function<void(const std::shared_ptr<fdf_power::ManualWakeLease>, async::Loop&, |
| const std::shared_ptr<SystemActivityGovernor>&, async::Loop&)> |
| test_func = [](const std::shared_ptr<fdf_power::ManualWakeLease> op, async::Loop& loop, |
| const std::shared_ptr<SystemActivityGovernor>& sag, async::Loop& sag_loop) { |
| EXPECT_TRUE(op->IsSuspended()); |
| EXPECT_TRUE(op->Start()); |
| EXPECT_TRUE(op->GetWakeLeaseCopy()->is_valid()); |
| EXPECT_TRUE(op->Start()); |
| EXPECT_TRUE(op->GetWakeLeaseCopy()->is_valid()); |
| loop.Quit(); |
| }; |
| DoWakeLeaseTest<fdf_power::ManualWakeLease>( |
| test_func, [](const std::shared_ptr<SystemActivityGovernor>& sag) {}); |
| } |
| |
| // Checks that when we are suspended we can start an ManualWakeLease and then |
| // end it without error. |
| TEST_F(WakeLeaseTest, TestManualWakeLeaseStartAndEndWhileSuspended) { |
| std::function<void(const std::shared_ptr<fdf_power::ManualWakeLease>, async::Loop&, |
| const std::shared_ptr<SystemActivityGovernor>&, async::Loop&)> |
| test_func = [](const std::shared_ptr<fdf_power::ManualWakeLease> op, async::Loop& loop, |
| const std::shared_ptr<SystemActivityGovernor>& sag, async::Loop& sag_loop) { |
| EXPECT_TRUE(op->IsSuspended()); |
| EXPECT_TRUE(op->Start()); |
| EXPECT_TRUE(op->End().is_ok()); |
| loop.Quit(); |
| }; |
| DoWakeLeaseTest<fdf_power::ManualWakeLease>( |
| test_func, [](const std::shared_ptr<SystemActivityGovernor>& sag) {}); |
| } |
| |
| // Tests what happens happens when an ManualWakeLease observes a resume signal |
| // and then we start an ManualWakeLease. We expect no lease is taken. After |
| // verifying that, send a suspend signal which should trigger the |
| // ManualWakeLease to claim a wake lease. We then verify that a wake lease was |
| // actually taken. |
| TEST_F(WakeLeaseTest, TestManagedWakeLeaseWhenResumedThenSuspend) { |
| async::Loop server_loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| server_loop.StartThread("server-loop"); |
| |
| async::Loop client_loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| client_loop.StartThread("client-loop"); |
| |
| // The server needs to outlive the client, so create references that exist |
| // until after the client's work concludes |
| std::shared_ptr<SystemActivityGovernor> sag_server; |
| std::shared_ptr<fidl::ServerBindingGroup<fuchsia_power_system::ActivityGovernor>> bindings; |
| fbl::RefPtr<fs::Service> sag; |
| |
| async::PostTask(server_loop.dispatcher(), [&client_loop, &server_loop, &sag_server, &bindings, |
| &sag]() mutable { |
| // First create SAG and related entities. |
| PrepFakeSag(sag, bindings, server_loop, sag_server); |
| |
| // Create a channel connected to client and server. |
| fidl::Endpoints<fuchsia_power_system::ActivityGovernor> sag_endpoints = |
| fidl::Endpoints<fuchsia_power_system::ActivityGovernor>::Create(); |
| sag->ConnectService(sag_endpoints.server.TakeChannel()); |
| |
| // Extract the channel from the client end, because passing a ClientEnd to |
| // another thread causes problems with thread unsafe FIDL bindings. |
| zx::channel client = sag_endpoints.client.TakeChannel(); |
| |
| // Tell the client to do its work. |
| async::PostTask(client_loop.dispatcher(), [&client_loop, &server_loop, |
| client = std::move(client), &sag_server]() mutable { |
| std::shared_ptr<fdf_power::ManualWakeLease> op = std::make_shared<fdf_power::ManualWakeLease>( |
| client_loop.dispatcher(), "test-operation", |
| fidl::ClientEnd<fuchsia_power_system::ActivityGovernor>(std::move(client))); |
| |
| // We want to test what ManualWakeLease does while the system is resumed, |
| // so have the server send the resume event. |
| async::PostTask(server_loop.dispatcher(), [&sag_server]() { sag_server->SendAfterResume(); }); |
| |
| // Trigger the start of the atomic operation. |
| async::PostTask(client_loop.dispatcher(), [op, &client_loop, &server_loop, &sag_server]() { |
| StartOperationWhenResumedThenSuspend(op, client_loop, sag_server, server_loop, |
| CheckLeaseAcquired); |
| }); |
| }); |
| }); |
| |
| // The client will quit its loop after doing its work, so wait for it. |
| client_loop.JoinThreads(); |
| |
| // On the server thread, destroy the server objects. |
| async::PostTask(server_loop.dispatcher(), [&sag_server, &bindings, &sag, &server_loop]() { |
| // Destroy all the shared objects on the thread where they were created. |
| sag.reset(); |
| bindings.reset(); |
| sag_server.reset(); |
| server_loop.Quit(); |
| }); |
| |
| // Shut down the server thread. |
| server_loop.JoinThreads(); |
| } |
| |
| // Verifies that a WakeLeaseProvider and WakeLease |
| // manage underlying resources as expected including that new |
| // WakeLeases are created when appropriate and the underlying |
| // WakeLease object is preserved. |
| TEST_F(WakeLeaseTest, WakeLeaseProviderTest) { |
| async::Loop server_loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| server_loop.StartThread("server-loop"); |
| |
| async::Loop client_loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| client_loop.StartThread("client-loop"); |
| |
| // The server needs to outlive the client, so create references that exist |
| // until after the client's work concludes |
| std::shared_ptr<SystemActivityGovernor> sag_server; |
| std::shared_ptr<fidl::ServerBindingGroup<fuchsia_power_system::ActivityGovernor>> bindings; |
| fbl::RefPtr<fs::Service> sag; |
| |
| async::PostTask(server_loop.dispatcher(), [&client_loop, &server_loop, &sag_server, &bindings, |
| &sag]() mutable { |
| // First create SAG and related entities. |
| PrepFakeSag(sag, bindings, server_loop, sag_server); |
| |
| // Create a channel connected to client and server. |
| fidl::Endpoints<fuchsia_power_system::ActivityGovernor> sag_endpoints = |
| fidl::Endpoints<fuchsia_power_system::ActivityGovernor>::Create(); |
| sag->ConnectService(sag_endpoints.server.TakeChannel()); |
| |
| // Extract the channel from the client end, because passing a ClientEnd to |
| // another thread causes problems with thread unsafe FIDL bindings. |
| zx::channel client = sag_endpoints.client.TakeChannel(); |
| |
| // Tell the client to do its work. |
| async::PostTask(client_loop.dispatcher(), [&client_loop, client = std::move(client)]() mutable { |
| std::shared_ptr<fdf_power::WakeLeaseProvider> operation_provider = |
| std::make_shared<fdf_power::WakeLeaseProvider>( |
| client_loop.dispatcher(), "test-operation", |
| fidl::ClientEnd<fuchsia_power_system::ActivityGovernor>(std::move(client))); |
| |
| std::shared_ptr<fdf_power::WakeLease> op1 = operation_provider->StartOperation(); |
| std::shared_ptr<fdf_power::WakeLease> op2 = operation_provider->StartOperation(); |
| |
| EXPECT_EQ(op1, op2); |
| |
| // Get a raw pointer to the current WakeLease to check |
| // against a different pointer later which will help us prove we dropped |
| // the WakeLease after all strong pointers to it were |
| // dropped. |
| fdf_power::WakeLease* old_addr = op1.get(); |
| std::shared_ptr<fdf_power::ManualWakeLease> first_lease = op1->GetWakeLease(); |
| op1.reset(); |
| op2.reset(); |
| |
| // It should be that we currently have no system wake lease since there |
| // are no valid WakeLeases. |
| EXPECT_TRUE(first_lease->GetWakeLeaseCopy().is_error()); |
| |
| // Start a new operation, which should create a new |
| // WakeLease. |
| std::shared_ptr<fdf_power::WakeLease> op3 = operation_provider->StartOperation(); |
| EXPECT_NE(old_addr, op3.get()); |
| |
| // The fdf_power::WakeLease should be the same, even though the |
| // WakeLease changed. |
| std::shared_ptr<fdf_power::ManualWakeLease> second_lease = op3->GetWakeLease(); |
| EXPECT_EQ(first_lease, second_lease); |
| |
| client_loop.Quit(); |
| }); |
| }); |
| |
| // The client will quit its loop after doing its work, so wait for it. |
| client_loop.JoinThreads(); |
| |
| // On the server thread, destroy the server objects. |
| async::PostTask(server_loop.dispatcher(), [&sag_server, &bindings, &sag, &server_loop]() { |
| // Destroy all the shared objects on the thread where they were created. |
| sag.reset(); |
| bindings.reset(); |
| sag_server.reset(); |
| server_loop.Quit(); |
| }); |
| |
| // Shut down the server thread. |
| server_loop.JoinThreads(); |
| } |
| |
| // Verify that an active TimeoutWakeLease doesn't take a system wake lease |
| // when we're resumed, but acquires a system wake lease across a |
| // resume->suspend transition: |
| // * Create a TimeoutWakeLease |
| // * Make the TimeoutWakeLease observe a resume signal |
| // * Call HandleInterrupt |
| // * Verify the TimeoutWakeLease has no system wake lease |
| // * Make the TimeoutWakeLease observe a suspend |
| // * Verify the TimeoutWakeLease now has a system wake lease |
| TEST_F(WakeLeaseTest, ActiveTimeoutWakeLeaseGetsLeaseOnSuspend) { |
| // This function gets run AFTER the one defined below. It calls itself again |
| // until the lease observes we're suspended. |
| const std::function<void(const std::shared_ptr<fdf_power::TimeoutWakeLease>, async::Loop&)> |
| run_after_suspend_observed = |
| [&run_after_suspend_observed](const std::shared_ptr<fdf_power::TimeoutWakeLease> op, |
| async::Loop& client_loop) { |
| if (op->IsResumed()) { |
| async::PostDelayedTask( |
| client_loop.dispatcher(), |
| [&run_after_suspend_observed, op, &client_loop]() { |
| run_after_suspend_observed(op, client_loop); |
| }, |
| zx::msec(100)); |
| return; |
| } |
| |
| // Should have acquired a wake lease on suspension. |
| EXPECT_FALSE(op->TakeWakeLease().is_error()); |
| client_loop.Quit(); |
| }; |
| |
| // This function is run after the test environment is set up. It waits until |
| // the lease observes we've resumed. After that it uses HandleInterrupt, |
| // which won't take a wake lease until we see a suspend. After that it |
| // triggers a suspend and starts the run_after_suspend_observed function. |
| const std::function<void(const std::shared_ptr<fdf_power::TimeoutWakeLease>, async::Loop&, |
| const std::shared_ptr<SystemActivityGovernor>&, async::Loop&)> |
| run_after_resume_observed = |
| [&run_after_resume_observed, &run_after_suspend_observed]( |
| const std::shared_ptr<fdf_power::TimeoutWakeLease> op, async::Loop& client_loop, |
| const std::shared_ptr<SystemActivityGovernor>& sag, async::Loop& sag_loop) { |
| if (!op->IsResumed()) { |
| async::PostDelayedTask( |
| client_loop.dispatcher(), |
| [op, &client_loop, &sag, &sag_loop, &run_after_resume_observed]() { |
| run_after_resume_observed(op, client_loop, sag, sag_loop); |
| }, |
| zx::msec(100)); |
| return; |
| } |
| |
| // This shouldn't acquire a lease because the system is resumed. |
| op->HandleInterrupt(zx::duration::infinite()); |
| EXPECT_TRUE(op->GetWakeLeaseCopy().is_error()); |
| |
| // Have the fake SAG tell teh wake lease we've suspended. |
| async::PostTask(sag_loop.dispatcher(), [&sag]() { sag->SendBeforeSuspend(); }); |
| |
| // Run the function that will wait to observe the suspend and then |
| // confirms we didn't take a wake lease. |
| run_after_suspend_observed(op, client_loop); |
| }; |
| |
| DoWakeLeaseTest<fdf_power::TimeoutWakeLease>( |
| run_after_resume_observed, |
| [](const std::shared_ptr<SystemActivityGovernor>& sag) { sag->SendAfterResume(); }); |
| } |
| |
| // Verify that an inactive TimeoutWakeLease does nothing across a |
| // resume->suspend transition: |
| // * Create a TimeoutWakeLease |
| // * Make the TimeoutWakeLease observe a resume signal |
| // * Give it a lease event pair |
| // * Take the lease event pair |
| // * Make the TimeoutWakeLease observe a suspend |
| // * Verify the TimeoutWakeLease contains no actual wake lease. |
| TEST_F(WakeLeaseTest, InactiveTimeoutWakeLeaseDoesNothingOnSuspend) { |
| // This function gets run AFTER the one defined below. It calls itself again |
| // until the lease observes we're suspended. |
| const std::function<void(const std::shared_ptr<fdf_power::TimeoutWakeLease>, async::Loop&)> |
| run_after_suspend_observed = |
| [&run_after_suspend_observed](const std::shared_ptr<fdf_power::TimeoutWakeLease> op, |
| async::Loop& client_loop) { |
| if (op->IsResumed()) { |
| async::PostDelayedTask( |
| client_loop.dispatcher(), |
| [&run_after_suspend_observed, op, &client_loop]() { |
| run_after_suspend_observed(op, client_loop); |
| }, |
| zx::msec(100)); |
| return; |
| } |
| |
| // It should be that the system suspending did NOT cause us to acquire |
| // a new wake lease and therefore we should have none. Check that we |
| // get an error. |
| EXPECT_TRUE(op->TakeWakeLease().is_error()); |
| client_loop.Quit(); |
| }; |
| |
| zx::eventpair h1, h2; |
| zx::eventpair::create(0, &h1, &h2); |
| |
| // This function is run after the test environment is set up. It waits until |
| // the lease observes we've resumed. After that it deposits a wait lease and |
| // takes it back, meaning the wake lease should not be active. After that it |
| // triggers a suspend and starts running `run_after_suspend_observed`. |
| const std::function<void(const std::shared_ptr<fdf_power::TimeoutWakeLease>, async::Loop&, |
| const std::shared_ptr<SystemActivityGovernor>&, async::Loop&)> |
| run_after_resume_observed = |
| [&run_after_resume_observed, &h1, &run_after_suspend_observed]( |
| const std::shared_ptr<fdf_power::TimeoutWakeLease> op, async::Loop& client_loop, |
| const std::shared_ptr<SystemActivityGovernor>& sag, async::Loop& sag_loop) { |
| if (!op->IsResumed()) { |
| async::PostDelayedTask( |
| client_loop.dispatcher(), |
| [op, &client_loop, &sag, &sag_loop, &run_after_resume_observed]() { |
| run_after_resume_observed(op, client_loop, sag, sag_loop); |
| }, |
| zx::msec(100)); |
| return; |
| } |
| |
| op->DepositWakeLease(std::move(h1), zx::time::infinite()); |
| // By taking the wake lease here we expect that when we suspend |
| // we won't acquire a new wake lease. |
| auto discard = op->TakeWakeLease(); |
| |
| // Have the fake SAG tell teh wake lease we've suspended. |
| async::PostTask(sag_loop.dispatcher(), [&sag]() { sag->SendBeforeSuspend(); }); |
| |
| // Run the function that will wait to observe the suspend and then |
| // confirms we didn't take a wake lease. |
| run_after_suspend_observed(op, client_loop); |
| }; |
| |
| DoWakeLeaseTest<fdf_power::TimeoutWakeLease>( |
| run_after_resume_observed, |
| [](const std::shared_ptr<SystemActivityGovernor>& sag) { sag->SendAfterResume(); }); |
| } |
| |
| TEST_F(WakeLeaseTest, TestWakeLeaseTimeouts) { |
| fidl::Endpoints<fuchsia_power_system::ActivityGovernor> endpoints = |
| fidl::Endpoints<fuchsia_power_system::ActivityGovernor>(); |
| // We're exploiting properties of the WakeLease implementation here, in |
| // particular that it only talks to SAG, which we aren't faking, if it |
| // needs a lease, not if it already has one. This means that WakeLease can |
| // hold the contradictory thoughts in its head that we have a wake lease, |
| // because it was deposited, but we are also suspended, because it hasn't |
| // been told otherwise. Neat! |
| |
| fdf_power::TimeoutWakeLease test_lease = fdf_power::TimeoutWakeLease( |
| async_get_default_dispatcher(), "test-lease", std::move(endpoints.client)); |
| |
| EXPECT_EQ(test_lease.GetNextTimeout(), ZX_TIME_INFINITE); |
| zx::eventpair h1, h2; |
| zx::eventpair::create(0, &h1, &h2); |
| test_lease.DepositWakeLease(std::move(h1), zx::time::infinite()); |
| h1 = test_lease.TakeWakeLease().value(); |
| test_lease.SetSuspended(true); |
| EXPECT_TRUE(test_lease.TakeWakeLease().is_error()); |
| |
| // TODO this might be unnecessary |
| test_lease.SetSuspended(false); |
| |
| EXPECT_EQ(test_lease.GetNextTimeout(), ZX_TIME_INFINITE); |
| zx::eventpair h3; |
| zx::eventpair::create(0, &h1, &h2); |
| h1.duplicate(ZX_RIGHT_SAME_RIGHTS, &h3); |
| zx::time expire_time = zx::clock::get_monotonic() + zx::hour(1); |
| test_lease.DepositWakeLease(std::move(h3), expire_time); |
| EXPECT_EQ(test_lease.GetNextTimeout(), expire_time.get()); |
| |
| h1.duplicate(ZX_RIGHT_SAME_RIGHTS, &h3); |
| zx::time expire_time2 = expire_time - zx::min(1); |
| test_lease.DepositWakeLease(std::move(h3), expire_time2); |
| EXPECT_EQ(test_lease.GetNextTimeout(), expire_time.get()); |
| |
| h1.duplicate(ZX_RIGHT_SAME_RIGHTS, &h3); |
| zx::time expire_time3 = expire_time + zx::min(1); |
| test_lease.DepositWakeLease(std::move(h3), expire_time3); |
| EXPECT_EQ(test_lease.GetNextTimeout(), expire_time3.get()); |
| |
| // Acquire a wake lease, specifying a timeout. The time the timeout is set |
| // for should be between the time before the call and the time after the |
| // call, plus the timeout. |
| zx::duration timeout = zx::min(10); |
| zx::time before = zx::clock::get_monotonic(); |
| test_lease.AcquireWakeLease(timeout); |
| zx::time after = zx::clock::get_monotonic(); |
| EXPECT_GE(test_lease.GetNextTimeout(), (before + timeout).get()); |
| EXPECT_LE(test_lease.GetNextTimeout(), (after + timeout).get()); |
| |
| // Acquire a new wake lease, but with a shorter timeout, which we expect to |
| // be changed. |
| timeout = zx::min(5); |
| before = zx::clock::get_monotonic(); |
| test_lease.AcquireWakeLease(timeout); |
| after = zx::clock::get_monotonic(); |
| zx_time_t next_timeout = test_lease.GetNextTimeout(); |
| EXPECT_GE(next_timeout, (before + timeout).get()); |
| EXPECT_LE(next_timeout, (after + timeout).get()); |
| |
| // Check that the lease handle zircon object does not change. Since we |
| // have a lease handle, we expect that calling HandleInterrupt won't obtain |
| // a new one. |
| auto lease_handle = test_lease.GetWakeLeaseCopy(); |
| EXPECT_FALSE(lease_handle.is_error()); |
| zx_info_handle_basic_t handle_info; |
| EXPECT_EQ(ZX_OK, lease_handle.value().get_info(ZX_INFO_HANDLE_BASIC, &handle_info, |
| sizeof(handle_info), nullptr, nullptr)); |
| zx_koid_t koid = handle_info.koid; |
| // Since the wake lease has never heard anything about whether the system is |
| // suspended or resumed, it assumes we're suspended, tell it we're resumed |
| test_lease.SetSuspended(false); |
| |
| // Now tell it we need to keep the system awake until a given time |
| before = zx::clock::get_monotonic(); |
| EXPECT_TRUE(test_lease.HandleInterrupt(timeout)); |
| |
| lease_handle = test_lease.GetWakeLeaseCopy(); |
| EXPECT_FALSE(lease_handle.is_error()); |
| EXPECT_EQ(ZX_OK, lease_handle.value().get_info(ZX_INFO_HANDLE_BASIC, &handle_info, |
| sizeof(handle_info), nullptr, nullptr)); |
| EXPECT_EQ(handle_info.koid, koid); |
| // Add a little slack here because we get some small delays later on |
| // since the implementation translates from an absolute time to an offset |
| // and then posts the task at that offset a bit after calculating it. Local |
| // testing indicates tens of microseconds of skew. |
| after = zx::clock::get_monotonic() + zx::sec(2); |
| |
| // Check that we still get the right timeout |
| next_timeout = test_lease.GetNextTimeout(); |
| EXPECT_GE(next_timeout, (before + timeout).get()); |
| EXPECT_LE(next_timeout, (after + timeout).get()); |
| } |
| } // namespace power_lib_test |