| // Copyright 2019 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 "task.h" |
| |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/async/default.h> |
| |
| #include <zxtest/zxtest.h> |
| |
| namespace { |
| |
| // A task that counts how many times Run() and DependencyFailed() are called. |
| class CountingTask : public devmgr::Task { |
| public: |
| CountingTask() : Task(async_get_default_dispatcher()) {} |
| ~CountingTask() override = default; |
| |
| size_t run_calls() const { return run_calls_; } |
| size_t dep_fail_calls() const { return dep_fail_calls_; } |
| |
| protected: |
| void Run() override { ++run_calls_; } |
| |
| void DependencyFailed(zx_status_t status) override { ++dep_fail_calls_; } |
| |
| private: |
| size_t run_calls_ = 0; |
| size_t dep_fail_calls_ = 0; |
| }; |
| |
| // A task with no dependencies |
| class NoDepsTask : public CountingTask { |
| public: |
| static fbl::RefPtr<NoDepsTask> Create(zx_status_t status) { |
| auto task = fbl::MakeRefCounted<NoDepsTask>(); |
| task->mock_status_ = status; |
| return task; |
| } |
| |
| private: |
| void Run() override { |
| CountingTask::Run(); |
| Complete(mock_status_); |
| } |
| |
| zx_status_t mock_status_; |
| }; |
| |
| // A task with a variable number of dependencies, each of which have 0 |
| // dependencies. |
| class DepsTask : public CountingTask { |
| public: |
| static fbl::RefPtr<DepsTask> Create(size_t deps_count, zx_status_t* dep_statuses, |
| bool fail_on_dep_failure) { |
| auto task = fbl::MakeRefCounted<DepsTask>(); |
| task->fail_on_dep_failure_ = fail_on_dep_failure; |
| for (size_t i = 0; i < deps_count; ++i) { |
| task->AddDependency(NoDepsTask::Create(dep_statuses[i])); |
| } |
| return task; |
| } |
| |
| private: |
| void Run() override { |
| CountingTask::Run(); |
| Complete(ZX_OK); |
| } |
| |
| void DependencyFailed(zx_status_t status) override { |
| CountingTask::DependencyFailed(status); |
| if (fail_on_dep_failure_) { |
| Complete(status); |
| } |
| } |
| |
| bool fail_on_dep_failure_; |
| }; |
| |
| // A task which is dependent on their parent task. |
| class DepOnParentTask : public CountingTask { |
| public: |
| // The created descendents will be stored in |out_deps|, beginning at |start_index|. |
| static fbl::RefPtr<DepOnParentTask> Create(zx_status_t root_status, size_t num_descendents, |
| fbl::Vector<fbl::RefPtr<DepOnParentTask>>* out_deps, |
| size_t start_index = 0) { |
| auto task = fbl::MakeRefCounted<DepOnParentTask>(); |
| task->mock_status_ = root_status; |
| if (num_descendents > 0) { |
| auto child_task = |
| DepOnParentTask::Create(ZX_OK, num_descendents - 1, out_deps, start_index + 1); |
| child_task->AddDependency(task); |
| out_deps->push_back(child_task); |
| } |
| return task; |
| } |
| |
| private: |
| void Run() override { |
| CountingTask::Run(); |
| Complete(mock_status_); |
| } |
| |
| void DependencyFailed(zx_status_t status) override { |
| CountingTask::DependencyFailed(status); |
| Complete(status); |
| } |
| |
| zx_status_t mock_status_; |
| }; |
| |
| class SequenceTask : public devmgr::Task { |
| public: |
| SequenceTask() : Task(async_get_default_dispatcher()) {} |
| |
| struct TaskDesc { |
| size_t dependency_count; |
| TaskDesc* dependencies; |
| bool complete = false; |
| }; |
| static fbl::RefPtr<SequenceTask> Create(TaskDesc* desc) { |
| auto task = fbl::MakeRefCounted<SequenceTask>(); |
| task->desc_ = desc; |
| for (size_t i = 0; i < desc->dependency_count; ++i) { |
| task->AddDependency(SequenceTask::Create(&desc->dependencies[i])); |
| } |
| return task; |
| } |
| |
| private: |
| void Run() override { |
| for (size_t i = 0; i < desc_->dependency_count; ++i) { |
| EXPECT_TRUE(desc_->dependencies[i].complete); |
| } |
| desc_->complete = true; |
| Complete(ZX_OK); |
| } |
| |
| TaskDesc* desc_; |
| }; |
| |
| class TaskTestCase : public zxtest::Test { |
| public: |
| async::Loop& loop() { return loop_; } |
| |
| private: |
| async::Loop loop_{&kAsyncLoopConfigAttachToCurrentThread}; |
| }; |
| |
| TEST_F(TaskTestCase, NoDependenciesDeferred) { |
| auto task = NoDepsTask::Create(ZX_OK); |
| ASSERT_FALSE(task->is_completed()); |
| EXPECT_EQ(task->status(), ZX_ERR_UNAVAILABLE); |
| loop().RunUntilIdle(); |
| } |
| |
| TEST_F(TaskTestCase, NoDependenciesSuccess) { |
| auto task = NoDepsTask::Create(ZX_OK); |
| loop().RunUntilIdle(); |
| ASSERT_TRUE(task->is_completed()); |
| EXPECT_OK(task->status()); |
| EXPECT_EQ(task->run_calls(), 1); |
| EXPECT_EQ(task->dep_fail_calls(), 0); |
| } |
| |
| TEST_F(TaskTestCase, NoDependenciesFailure) { |
| auto task = NoDepsTask::Create(ZX_ERR_NOT_FOUND); |
| loop().RunUntilIdle(); |
| ASSERT_TRUE(task->is_completed()); |
| EXPECT_EQ(task->status(), ZX_ERR_NOT_FOUND); |
| EXPECT_EQ(task->run_calls(), 1); |
| EXPECT_EQ(task->dep_fail_calls(), 0); |
| } |
| |
| TEST_F(TaskTestCase, SuccessfulDependencies) { |
| zx_status_t statuses[] = {ZX_OK, ZX_OK, ZX_OK}; |
| auto task = DepsTask::Create(fbl::count_of(statuses), statuses, true); |
| loop().RunUntilIdle(); |
| ASSERT_TRUE(task->is_completed()); |
| EXPECT_OK(task->status()); |
| EXPECT_EQ(task->run_calls(), 1); |
| EXPECT_EQ(task->dep_fail_calls(), 0); |
| } |
| |
| TEST_F(TaskTestCase, FailedDependenciesIgnored) { |
| zx_status_t statuses[] = {ZX_OK, ZX_ERR_NOT_FOUND, ZX_ERR_INVALID_ARGS}; |
| auto task = DepsTask::Create(fbl::count_of(statuses), statuses, false); |
| loop().RunUntilIdle(); |
| ASSERT_TRUE(task->is_completed()); |
| EXPECT_OK(task->status()); |
| EXPECT_EQ(task->run_calls(), 1); |
| EXPECT_EQ(task->dep_fail_calls(), 2); |
| } |
| |
| TEST_F(TaskTestCase, FailedDependenciesPropagate) { |
| zx_status_t statuses[] = {ZX_OK, ZX_ERR_NOT_FOUND, ZX_ERR_INVALID_ARGS}; |
| auto task = DepsTask::Create(fbl::count_of(statuses), statuses, true); |
| loop().RunUntilIdle(); |
| ASSERT_TRUE(task->is_completed()); |
| EXPECT_EQ(task->status(), ZX_ERR_NOT_FOUND); |
| EXPECT_EQ(task->run_calls(), 0); |
| EXPECT_EQ(task->dep_fail_calls(), 1); |
| } |
| |
| TEST_F(TaskTestCase, DependencySequencing) { |
| SequenceTask::TaskDesc child1_children[] = { |
| {0, nullptr}, |
| }; |
| SequenceTask::TaskDesc root_children[] = { |
| {fbl::count_of(child1_children), child1_children}, |
| {0, nullptr}, |
| }; |
| |
| SequenceTask::TaskDesc root = {fbl::count_of(root_children), root_children}; |
| auto task = SequenceTask::Create(&root); |
| loop().RunUntilIdle(); |
| ASSERT_TRUE(task->is_completed()); |
| EXPECT_OK(task->status()); |
| EXPECT_TRUE(root.complete); |
| for (auto& child : root_children) { |
| EXPECT_TRUE(child.complete); |
| } |
| for (auto& child : child1_children) { |
| EXPECT_TRUE(child.complete); |
| } |
| } |
| |
| TEST_F(TaskTestCase, DependencyTracking) { |
| zx_status_t statuses[] = {ZX_OK, ZX_ERR_NOT_FOUND}; |
| auto task = DepsTask::Create(fbl::count_of(statuses), statuses, false); |
| ASSERT_EQ(task->Dependencies().size(), 2); |
| loop().RunUntilIdle(); |
| ASSERT_TRUE(task->is_completed()); |
| ASSERT_EQ(task->Dependencies().size(), 0); |
| } |
| |
| TEST_F(TaskTestCase, DependentOnParentSuccess) { |
| size_t num_deps = 10; |
| fbl::Vector<fbl::RefPtr<DepOnParentTask>> deps; |
| auto root_task = DepOnParentTask::Create(ZX_OK, num_deps, &deps); |
| |
| loop().RunUntilIdle(); |
| |
| ASSERT_TRUE(root_task->is_completed()); |
| EXPECT_OK(root_task->status()); |
| EXPECT_EQ(root_task->run_calls(), 1); |
| EXPECT_EQ(root_task->dep_fail_calls(), 0); |
| |
| for (auto task : deps) { |
| ASSERT_TRUE(task->is_completed()); |
| EXPECT_OK(task->status()); |
| EXPECT_EQ(task->run_calls(), 1); |
| EXPECT_EQ(task->dep_fail_calls(), 0); |
| } |
| } |
| |
| TEST_F(TaskTestCase, DependentOnParentFailure) { |
| size_t num_deps = 10; |
| fbl::Vector<fbl::RefPtr<DepOnParentTask>> deps; |
| auto root_task = DepOnParentTask::Create(ZX_ERR_BAD_STATE, num_deps, &deps); |
| |
| loop().RunUntilIdle(); |
| |
| ASSERT_TRUE(root_task->is_completed()); |
| EXPECT_NOT_OK(root_task->status()); |
| EXPECT_EQ(root_task->run_calls(), 1); |
| EXPECT_EQ(root_task->dep_fail_calls(), 0); |
| |
| for (auto task : deps) { |
| ASSERT_TRUE(task->is_completed()); |
| EXPECT_NOT_OK(task->status()); |
| EXPECT_EQ(task->run_calls(), 0); |
| EXPECT_EQ(task->dep_fail_calls(), 1); |
| } |
| } |
| |
| } // namespace |