blob: 2bf229328171c546ee24d177b2340be8602b064d [file] [log] [blame]
// Copyright 2017 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-utils/walker.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/object.h>
#include <unittest/unittest.h>
namespace {
bool is_valid_handle(zx_handle_t handle) {
return zx_object_get_info(handle, ZX_INFO_HANDLE_VALID, nullptr, 0, nullptr, nullptr) == ZX_OK;
}
// TestTaskEnumerator ctor flags.
static constexpr unsigned int HAS_ON_JOB = 1u << 0;
static constexpr unsigned int HAS_ON_PROCESS = 1u << 1;
static constexpr unsigned int HAS_ON_THREAD = 1u << 2;
// An enumerator that does basic validation and allows for turning on and off
// job/process/thread callbacks.
class TestTaskEnumerator : public TaskEnumerator {
public:
// |flags| is a bitmask of HAS_ON_* values indicating the values that
// corresponding has_on_*() methods should return.
TestTaskEnumerator(unsigned int flags) : flags_(flags) {}
// Checks postconditions, marking the current test as failed
// if any are not met.
virtual void Validate() const {
if (has_on_job()) {
EXPECT_GT(jobs_seen_, 0);
} else {
EXPECT_EQ(jobs_seen_, 0);
}
if (has_on_process()) {
EXPECT_GT(processes_seen_, 0);
} else {
EXPECT_EQ(processes_seen_, 0);
}
if (has_on_thread()) {
EXPECT_GT(threads_seen_, 0);
} else {
EXPECT_EQ(threads_seen_, 0);
}
}
protected:
virtual zx_status_t OnJob(int depth, zx_handle_t job, zx_koid_t koid,
zx_koid_t parent_koid) override {
EXPECT_TRUE(has_on_job());
EXPECT_GE(depth, 0);
EXPECT_TRUE(is_valid_handle(job));
EXPECT_NE(koid, 0);
if (depth == 0) {
EXPECT_EQ(parent_koid, 0, "root job");
} else {
EXPECT_NE(parent_koid, 0, "non-root job");
}
jobs_seen_++;
return ZX_OK;
}
virtual zx_status_t OnProcess(int depth, zx_handle_t process, zx_koid_t koid,
zx_koid_t parent_koid) override {
EXPECT_TRUE(has_on_process());
EXPECT_GT(depth, 0, "process depth should always be > 0");
EXPECT_TRUE(is_valid_handle(process));
EXPECT_NE(koid, 0);
EXPECT_NE(parent_koid, 0);
processes_seen_++;
return ZX_OK;
}
virtual zx_status_t OnThread(int depth, zx_handle_t thread, zx_koid_t koid,
zx_koid_t parent_koid) override {
EXPECT_TRUE(has_on_thread());
EXPECT_GT(depth, 1, "thread depth should always be > 1");
EXPECT_TRUE(is_valid_handle(thread));
EXPECT_NE(koid, 0);
EXPECT_NE(parent_koid, 0);
threads_seen_++;
return ZX_OK;
}
private:
bool has_on_job() const final { return flags_ & HAS_ON_JOB; }
bool has_on_process() const final { return flags_ & HAS_ON_PROCESS; }
bool has_on_thread() const final { return flags_ & HAS_ON_THREAD; }
const unsigned int flags_;
int jobs_seen_ = 0;
int processes_seen_ = 0;
int threads_seen_ = 0;
};
template <unsigned int Flags>
bool basic_cpp_walk() {
BEGIN_TEST;
TestTaskEnumerator tte(Flags);
// TODO(dbort): Build a job tree just for the test and walk that instead;
// same for other tests in this file. utest/core/object-info and
// utest/policy (and maybe more) already do their own test job-tree
// building; create a common helper lib.
EXPECT_EQ(tte.WalkRootJobTree(), ZX_OK);
tte.Validate();
END_TEST;
}
// A subclass of TestTaskEnumerator that will return a non-ZX_OK status
// at some point, demonstrating that the walk stops and the status value
// is passed to the caller.
class FailingTaskEnumerator : public TestTaskEnumerator {
public:
FailingTaskEnumerator(unsigned int flags, int poison_depth)
: TestTaskEnumerator(flags), poison_depth_(poison_depth) {}
// An unusual error code not used by the base class.
static constexpr zx_status_t FailingStatus = ZX_ERR_STOP;
private:
// Not worth calling since the walk will stop before completing.
void Validate() const override { EXPECT_TRUE(false); }
zx_status_t OnJob(int depth, zx_handle_t job, zx_koid_t koid, zx_koid_t parent_koid) override {
EXPECT_FALSE(poisoned_);
return MaybePoison(depth, TestTaskEnumerator::OnJob(depth, job, koid, parent_koid));
}
zx_status_t OnProcess(int depth, zx_handle_t process, zx_koid_t koid,
zx_koid_t parent_koid) override {
EXPECT_FALSE(poisoned_);
return MaybePoison(depth, TestTaskEnumerator::OnProcess(depth, process, koid, parent_koid));
}
zx_status_t OnThread(int depth, zx_handle_t thread, zx_koid_t koid,
zx_koid_t parent_koid) override {
EXPECT_FALSE(poisoned_);
return MaybePoison(depth, TestTaskEnumerator::OnThread(depth, thread, koid, parent_koid));
}
zx_status_t MaybePoison(int depth, zx_status_t s) {
if (s == ZX_OK && depth >= poison_depth_) {
poisoned_ = true;
return FailingStatus;
}
return s;
}
const int poison_depth_;
bool poisoned_ = false;
};
template <unsigned int Flags, int PoisonDepth>
bool cpp_walk_failure() {
BEGIN_TEST;
FailingTaskEnumerator fte(Flags, PoisonDepth);
EXPECT_EQ(fte.WalkRootJobTree(), FailingTaskEnumerator::FailingStatus);
END_TEST;
}
} // namespace
// NOTE: Since the C++ API is built on top of the C API, this provides decent
// coverage for the C API without testing it directly.
BEGIN_TEST_CASE(task_utils)
RUN_TEST(basic_cpp_walk<0>)
RUN_TEST(basic_cpp_walk<HAS_ON_JOB>)
RUN_TEST(basic_cpp_walk<HAS_ON_JOB | HAS_ON_PROCESS>)
RUN_TEST(basic_cpp_walk<HAS_ON_JOB | HAS_ON_THREAD>)
RUN_TEST(basic_cpp_walk<HAS_ON_JOB | HAS_ON_PROCESS | HAS_ON_THREAD>)
RUN_TEST(basic_cpp_walk<HAS_ON_PROCESS>)
RUN_TEST(basic_cpp_walk<HAS_ON_PROCESS | HAS_ON_THREAD>)
RUN_TEST(basic_cpp_walk<HAS_ON_THREAD>)
// The callback on the root job happens on a different code path than other job
// depths, so test it explicitly.
RUN_TEST((cpp_walk_failure<HAS_ON_JOB, /*PoisonDepth=*/0>))
// A minimal system doesn't have jobs deeper than depth 1.
// TODO(dbort): Use depth 2 or more for all types once we have a test job
// hierarchy instead of the root job.
RUN_TEST((cpp_walk_failure<HAS_ON_JOB, /*PoisonDepth=*/1>))
RUN_TEST((cpp_walk_failure<HAS_ON_PROCESS, /*PoisonDepth=*/2>))
RUN_TEST((cpp_walk_failure<HAS_ON_THREAD, /*PoisonDepth=*/2>))
END_TEST_CASE(task_utils)