| // Copyright 2026 The Fuchsia Authors |
| // |
| // Use of this source code is governed by a MIT-style |
| // license that can be found in the LICENSE file or at |
| // https://opensource.org/licenses/MIT |
| |
| #include <lib/unittest/unittest.h> |
| |
| #include <kernel/scheduler.h> |
| #include <kernel/thread.h> |
| |
| namespace { |
| |
| struct TestContext { |
| const Thread* exclude_thread; |
| }; |
| |
| } // namespace |
| |
| // Helper class to access private Scheduler State and Scheduler members. |
| class SchedulerRunQueueTestHelper { |
| public: |
| static void SetTimes(Thread* t, SchedTime start, SchedTime finish) TA_NO_THREAD_SAFETY_ANALYSIS { |
| t->scheduler_state().start_time_ = start; |
| t->scheduler_state().finish_time_ = finish; |
| } |
| |
| static bool RunTest() TA_NO_THREAD_SAFETY_ANALYSIS { |
| BEGIN_TEST; |
| |
| // Create a RunQueue instance. |
| Scheduler::RunQueue rq; |
| EXPECT_TRUE(rust_kernel_run_queue_is_empty(&rq), "Queue should be empty initially"); |
| EXPECT_EQ(0u, rust_kernel_run_queue_size(&rq), "Queue size should be 0 initially"); |
| EXPECT_NULL(rust_kernel_run_queue_front(&rq), "Queue front should be null initially"); |
| EXPECT_NULL(rust_kernel_run_queue_root(&rq), "Queue root should be null initially"); |
| |
| // Create three threads. We don't start them. |
| Thread* t1 = |
| Thread::Create("test_t1", [](void*) -> int { return 0; }, nullptr, DEFAULT_PRIORITY); |
| Thread* t2 = |
| Thread::Create("test_t2", [](void*) -> int { return 0; }, nullptr, DEFAULT_PRIORITY); |
| Thread* t3 = |
| Thread::Create("test_t3", [](void*) -> int { return 0; }, nullptr, DEFAULT_PRIORITY); |
| |
| ASSERT_NONNULL(t1); |
| ASSERT_NONNULL(t2); |
| ASSERT_NONNULL(t3); |
| |
| // Initialize scheduler state. |
| Scheduler::InitializeThread(t1, SchedulerState::BaseProfile{DEFAULT_PRIORITY}); |
| Scheduler::InitializeThread(t2, SchedulerState::BaseProfile{DEFAULT_PRIORITY}); |
| Scheduler::InitializeThread(t3, SchedulerState::BaseProfile{DEFAULT_PRIORITY}); |
| |
| // Set custom start/finish times to test order and eligibility. |
| // t1: start = 10, finish = 20 |
| // t2: start = 20, finish = 30 |
| // t3: start = 15, finish = 25 |
| SetTimes(t1, SchedTime{10}, SchedTime{20}); |
| SetTimes(t2, SchedTime{20}, SchedTime{30}); |
| SetTimes(t3, SchedTime{15}, SchedTime{25}); |
| |
| // Insert them into the run queue. |
| rust_kernel_run_queue_insert(&rq, t1); |
| rust_kernel_run_queue_insert(&rq, t2); |
| rust_kernel_run_queue_insert(&rq, t3); |
| |
| EXPECT_FALSE(rust_kernel_run_queue_is_empty(&rq), "Queue should not be empty"); |
| EXPECT_EQ(3u, rust_kernel_run_queue_size(&rq), "Queue size should be 3"); |
| EXPECT_EQ(t1, rust_kernel_run_queue_front(&rq), "Queue front should be t1"); |
| EXPECT_NONNULL(rust_kernel_run_queue_root(&rq), "Queue root should not be null"); |
| |
| // Predicate that always returns true. |
| auto true_pred = [](const Thread*, const void*) { return true; }; |
| |
| // Test case 1: eligible_time = 9. |
| // No thread is eligible because front (t1) start_time is 10. |
| { |
| Thread* result = rust_kernel_run_queue_find_earliest_eligible_thread(&rq, SchedTime{9}, |
| true_pred, nullptr); |
| EXPECT_NULL(result, "No thread should be eligible at time 9"); |
| } |
| |
| // Test case 2: eligible_time = 12. |
| // Only t1 is eligible. |
| { |
| Thread* result = rust_kernel_run_queue_find_earliest_eligible_thread(&rq, SchedTime{12}, |
| true_pred, nullptr); |
| EXPECT_EQ(t1, result, "t1 should be the only eligible thread at time 12"); |
| } |
| |
| // Test case 3: eligible_time = 16. |
| // t1 (finish 20) and t3 (finish 25) are eligible. |
| // Should return t1 because it has the earliest finish time. |
| { |
| Thread* result = rust_kernel_run_queue_find_earliest_eligible_thread(&rq, SchedTime{16}, |
| true_pred, nullptr); |
| EXPECT_EQ(t1, result, "t1 should be preferred over t3 at time 16 due to earlier finish time"); |
| } |
| |
| // Test case 4: eligible_time = 16 with predicate excluding t1. |
| // C++ FindEarliestEligibleThread does not backtrack, so if it decides to |
| // search a subtree (based on min_finish_time) and that subtree's best node |
| // is rejected by the predicate, it may fail (return nullptr) even if there |
| // are other eligible and acceptable nodes. Rust should match this behavior. |
| { |
| auto exclude_pred = [](const Thread* t, const void* ctx) { |
| auto context = static_cast<const TestContext*>(ctx); |
| return t != context->exclude_thread; |
| }; |
| TestContext ctx{t1}; |
| Thread* rust_result = rust_kernel_run_queue_find_earliest_eligible_thread(&rq, SchedTime{16}, |
| exclude_pred, &ctx); |
| EXPECT_NULL(rust_result, "Should return nullptr because C++ algorithm does not backtrack"); |
| } |
| |
| // Test case 5: Erase t1 and try again at time 16. |
| // Should return t3 even without exclusion predicate. |
| { |
| Thread* erased = rust_kernel_run_queue_erase(&rq, t1); |
| EXPECT_EQ(t1, erased, "Erase should return t1"); |
| |
| Thread* result = rust_kernel_run_queue_find_earliest_eligible_thread(&rq, SchedTime{16}, |
| true_pred, nullptr); |
| EXPECT_EQ(t3, result, "t3 should be chosen after t1 is erased"); |
| } |
| |
| // Test case 6: Iteration. |
| // Should visit t3, t2 in order (t1 was erased). |
| { |
| RunQueueIterator iter = rust_kernel_run_queue_iterator_create(&rq); |
| EXPECT_TRUE(rust_kernel_run_queue_iterator_is_valid(&iter), |
| "Iterator should be valid initially"); |
| EXPECT_EQ(t3, rust_kernel_run_queue_iterator_get(&iter), "First element should be t3"); |
| |
| rust_kernel_run_queue_iterator_next(&iter); |
| EXPECT_TRUE(rust_kernel_run_queue_iterator_is_valid(&iter), |
| "Iterator should be valid after first next"); |
| EXPECT_EQ(t2, rust_kernel_run_queue_iterator_get(&iter), "Second element should be t2"); |
| |
| rust_kernel_run_queue_iterator_next(&iter); |
| EXPECT_FALSE(rust_kernel_run_queue_iterator_is_valid(&iter), |
| "Iterator should be invalid after second next"); |
| EXPECT_NULL(rust_kernel_run_queue_iterator_get(&iter), "Element should be null after end"); |
| } |
| |
| // Clean up threads. We must erase them from the queue first. |
| rust_kernel_run_queue_erase(&rq, t2); |
| rust_kernel_run_queue_erase(&rq, t3); |
| t1->Forget(); |
| t2->Forget(); |
| t3->Forget(); |
| |
| END_TEST; |
| } |
| }; |
| |
| namespace { |
| |
| bool test_run_queue_basic() { return SchedulerRunQueueTestHelper::RunTest(); } |
| |
| } // namespace |
| |
| UNITTEST_START_TESTCASE(scheduler_run_queue_tests) |
| UNITTEST("run_queue_basic", test_run_queue_basic) |
| UNITTEST_END_TESTCASE(scheduler_run_queue_tests, "scheduler_run_queue", "Scheduler Run Queue tests") |