text: // 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 <lib/async/task.h>
  #include <lib/async/time.h>
  #include <zircon/time.h>
  
  #include <functional>
  #include <memory>
  #include <optional>
  
  #include <gtest/gtest.h>
  
  #include "src/connectivity/wlan/drivers/testing/lib/sim-env/sim-env.h"
  #include "src/connectivity/wlan/drivers/testing/lib/sim-env/sim-sta-ifc.h"
  
  // zx::time() gives us an absolute time of zero
  #define ABSOLUTE_TIME(delay) (zx::time() + (delay))
  
  namespace wlan::testing {
  namespace {
  
  constexpr zx::duration kSimulatedClockDuration = zx::sec(10);
  
  }  // namespace
  
  class EventTest : public ::testing::Test, public simulation::StationIfc {
   public:
    EventTest() : env_(std::make_unique<simulation::Environment>()), completed_(false) {
      env_->AddStation(this);
    }
    ~EventTest() {
      if (env_) {
        env_->RemoveStation(this);
      }
    };
  
    // StationIfc methods
    void Rx(std::shared_ptr<const simulation::SimFrame> frame,
            std::shared_ptr<const simulation::WlanRxInfo> info) override {}
  
    // Testing callbacks
    void SingleEventCallback();
    void PeriodicEventCallback();
    void SequentialEventCallback();
    void DuplicateTimeEventCallback(uint64_t value);
    void NotificationReceived(uint64_t value);
  
    std::unique_ptr<simulation::Environment> env_;
    bool completed_;
  };
  
  /*** Uneventful test ***/
  
  // Verify that we can run through a simulation with no events
  TEST_F(EventTest, Uneventful) {
    env_->Run(kSimulatedClockDuration);
    EXPECT_EQ(env_->GetTime(), ABSOLUTE_TIME(zx::usec(0)));
  }
  
  /*** SingleEvent test ***/
  
  void EventTest::SingleEventCallback() { completed_ = true; }
  
  // Test with a single event
  TEST_F(EventTest, SingleEvent) {
    constexpr zx::duration delay = zx::msec(100);
    EXPECT_EQ(env_->ScheduleNotification(std::bind(&EventTest::SingleEventCallback, this), delay),
              ZX_OK);
    env_->Run(kSimulatedClockDuration);
    EXPECT_EQ(completed_, true);
  
    // Time should stop at the last event
    EXPECT_EQ(env_->GetTime(), ABSOLUTE_TIME(delay));
  }
  
  /*** PeriodicEvents test ***/
  
  constexpr uint32_t kNumPeriodicEvents = 47;
  constexpr zx::duration kEventPeriod = zx::msec(78);
  
  void EventTest::PeriodicEventCallback() {
    static int32_t remaining_events = kNumPeriodicEvents;
    remaining_events--;
    if (remaining_events == 0) {
      completed_ = true;
    } else {
      EXPECT_GT(remaining_events, 0);
      EXPECT_EQ(env_->ScheduleNotification(std::bind(&EventTest::PeriodicEventCallback, this),
                                           kEventPeriod),
                ZX_OK);
    }
  }
  
  // Test with periodic events
  TEST_F(EventTest, PeriodicEvents) {
    EXPECT_EQ(
        env_->ScheduleNotification(std::bind(&EventTest::PeriodicEventCallback, this), kEventPeriod),
        ZX_OK);
    env_->Run(kSimulatedClockDuration);
    EXPECT_EQ(completed_, true);
    EXPECT_EQ(env_->GetTime(), ABSOLUTE_TIME(kEventPeriod * kNumPeriodicEvents));
  }
  
  /*** InvalidTime test ***/
  
  // Attempt to add event before current time
  TEST_F(EventTest, InvalidTime) {
    constexpr zx::duration delay = zx::msec(-100);
    EXPECT_EQ(env_->ScheduleNotification(std::bind(&EventTest::SingleEventCallback, this), delay),
              ZX_ERR_INVALID_ARGS);
    env_->Run(kSimulatedClockDuration);
    EXPECT_EQ(completed_, false);
  
    // Time should not have advanced
    EXPECT_EQ(env_->GetTime(), ABSOLUTE_TIME(zx::usec(0)));
  }
  
  /*** SequentialEvents test ***/
  
  constexpr int32_t kSequentialTestSecs = 99;
  
  void EventTest::SequentialEventCallback() {
    static int32_t remaining_events = kSequentialTestSecs + 1;
    // Invoked at time 0, then every second thereafter
    static uint32_t expected_time = 0;
    remaining_events--;
    if (remaining_events == 0) {
      completed_ = true;
    } else {
      EXPECT_GT(remaining_events, 0);
      EXPECT_EQ(env_->GetTime(), ABSOLUTE_TIME(zx::sec(expected_time)));
    }
    expected_time++;
  }
  
  // Verify that events can be added in any order and are processed sequentially
  TEST_F(EventTest, SequentialEvents) {
    // Add the even-second events
    for (int32_t i = 0; i <= kSequentialTestSecs; i += 2) {
      zx::duration delay = zx::sec(i);
      EXPECT_EQ(
          env_->ScheduleNotification(std::bind(&EventTest::SequentialEventCallback, this), delay),
          ZX_OK);
    }
    // Add the odd-second events
    for (int32_t i = (kSequentialTestSecs % 2) ? kSequentialTestSecs : (kSequentialTestSecs - 1);
         i > 0; i -= 2) {
      zx::duration delay = zx::sec(i);
      EXPECT_EQ(
          env_->ScheduleNotification(std::bind(&EventTest::SequentialEventCallback, this), delay),
          ZX_OK);
    }
    env_->Run(zx::sec(kSequentialTestSecs));
    EXPECT_EQ(completed_, true);
    EXPECT_EQ(env_->GetTime(), ABSOLUTE_TIME(zx::sec(kSequentialTestSecs)));
  }
  
  /*** DuplicateEvents test ***/
  
  constexpr uint32_t kDuplicateEventCount = 10;
  constexpr zx::duration kDuplicateEventsTime = zx::msec(120);
  
  void EventTest::DuplicateTimeEventCallback(uint64_t value) {
    static int64_t remaining_events = kDuplicateEventCount;
    static uint64_t expected_value = 0;
    remaining_events--;
    if (remaining_events == 0) {
      completed_ = true;
    } else {
      EXPECT_GT(remaining_events, 0);
      // Verify that events are processed in order
      EXPECT_EQ(expected_value, value);
    }
    expected_value++;
  }
  
  // Test multiple events at same time. Events should be processed in the order they were added.
  TEST_F(EventTest, DuplicateEvents) {
    for (uint32_t i = 0; i < kDuplicateEventCount; i++) {
      EXPECT_EQ(env_->ScheduleNotification(std::bind(&EventTest::DuplicateTimeEventCallback, this, i),
                                           kDuplicateEventsTime),
                ZX_OK);
    }
    env_->Run(kDuplicateEventsTime);
    EXPECT_EQ(completed_, true);
    EXPECT_EQ(env_->GetTime(), ABSOLUTE_TIME(kDuplicateEventsTime));
  }
  
  // Test cancelling events. Start with events scheduled at 1, 2, 3, 4, and 5 seconds into
  // simulation.
  //
  // Timeline:
  //   1s: Event fires, do nothing
  //   2s: Cancel event at time 4s (ZX_OK)
  //       Test calls to cancel with invalid args
  //   3s: Test calls to cancel with invalid args
  //   5s: Event fires, do nothing
  constexpr size_t kNotificationsCount = 5;
  
  static struct CancelNotificationState {
    bool notifications_seen[kNotificationsCount];
    uint64_t ids[kNotificationsCount];
  } cancel_state;
  
  void EventTest::NotificationReceived(uint64_t value) {
    ASSERT_LE(value, kNotificationsCount);
    cancel_state.notifications_seen[value] = true;
  
    if (value == 1) {  // 2s into the test
      // Try to cancel an already-passed event
      EXPECT_EQ(ZX_ERR_NOT_FOUND, env_->CancelNotification(cancel_state.ids[0]));
  
      // Try to cancel our current event
      EXPECT_EQ(ZX_ERR_NOT_FOUND, env_->CancelNotification(cancel_state.ids[1]));
  
      // Try to cancel a future event with incorrect ID
      uint64_t fake_id = 0x6b46616b654964;
      bool match_found;
      do {
        match_found = false;
        for (size_t i = 0; i < kNotificationsCount; i++) {
          if (fake_id == cancel_state.ids[i]) {
            match_found = true;
            fake_id++;
            break;
          }
        }
      } while (match_found);
      EXPECT_EQ(ZX_ERR_NOT_FOUND, env_->CancelNotification(fake_id));
  
      // Cancel a future event (hopefully successfully)
      EXPECT_EQ(ZX_OK, env_->CancelNotification(cancel_state.ids[3]));
    }
  
    if (value == 2) {  // 3s into the test
      // Try cancelling a previously-cancelled event
      EXPECT_EQ(ZX_ERR_NOT_FOUND, env_->CancelNotification(cancel_state.ids[3]));
    }
  }
  
  TEST_F(EventTest, CancelEvents) {
    // Create and initialize notification objects
    for (uint64_t i = 0; i < kNotificationsCount; i++) {
      cancel_state.notifications_seen[i] = false;
      EXPECT_EQ(ZX_OK,
                env_->ScheduleNotification(std::bind(&EventTest::NotificationReceived, this, i),
                                           zx::sec(i + 1), &cancel_state.ids[i]));
    }
  
    env_->Run(kSimulatedClockDuration);
  
    // Verify notification events seen (except the one at time 4, which should have been cancelled)
    for (uint64_t i = 0; i < kNotificationsCount; i++) {
      EXPECT_EQ(cancel_state.notifications_seen[i], (i == 3) ? false : true);
    }
  }
  
  // Test the event system, used through the async dispatcher API.
  TEST_F(EventTest, AsyncDispatcher) {
    // Set up a couple of tasks.
    struct Task : public async_task_t {
      explicit Task(zx_time_t deadline) {
        handler = [](async_dispatcher_t* dispatcher, async_task_t* task, zx_status_t status) {
          static_cast<Task*>(task)->status.emplace(status);
        };
        this->deadline = deadline;
      }
  
      std::optional<zx_status_t> status;
    };
    auto task1 = Task(ZX_HOUR(10));
    auto task2 = Task(ZX_HOUR(20));
    auto task3 = Task(ZX_HOUR(30));
    auto task4 = Task(ZX_HOUR(40));
  
    // Post the tasks in unsorted order, and then run the first one.
    EXPECT_EQ(ZX_OK, async_post_task(env_->GetDispatcher(), &task3));
    EXPECT_EQ(ZX_OK, async_post_task(env_->GetDispatcher(), &task1));
    EXPECT_EQ(ZX_OK, async_post_task(env_->GetDispatcher(), &task4));
    EXPECT_EQ(ZX_OK, async_post_task(env_->GetDispatcher(), &task2));
    env_->Run(zx::hour(15));
  
    // Cancel the second task, then run up to the third one.
    EXPECT_EQ(ZX_ERR_NOT_FOUND, async_cancel_task(env_->GetDispatcher(), &task1));
    EXPECT_EQ(ZX_OK, async_cancel_task(env_->GetDispatcher(), &task2));
    env_->Run(zx::hour(20));
  
    // Task 1 and task 3 both ran.
    EXPECT_TRUE(task1.status.has_value());
    EXPECT_TRUE(task3.status.has_value());
    EXPECT_EQ(ZX_OK, task1.status.value());
    EXPECT_EQ(ZX_OK, task3.status.value());
  
    // Task 2 was cancelled, task 4 hasn't fired yet.
    EXPECT_FALSE(task2.status.has_value());
    EXPECT_FALSE(task4.status.has_value());
  
    // Shut down the environment, and task 4 will get a shutdown notification.
    env_.reset();
    EXPECT_TRUE(task4.status.has_value());
    EXPECT_EQ(ZX_ERR_CANCELED, task4.status.value());
  }
  
  }  // namespace wlan::testing
  