| // 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) |