blob: b35888f5a438cd700f288a53eb52414c23482534 [file] [log] [blame]
// 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 <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