text: #ifndef AOS_CONTROLS_CONTROL_LOOP_TEST_H_
  #define AOS_CONTROLS_CONTROL_LOOP_TEST_H_
  
  #include <chrono>
  #include <string_view>
  
  #include "aos/events/simulated_event_loop.h"
  #include "aos/flatbuffers.h"
  #include "aos/json_to_flatbuffer.h"
  #include "aos/testing/test_logging.h"
  #include "aos/time/time.h"
  #include "frc971/input/joystick_state_generated.h"
  #include "frc971/input/robot_state_generated.h"
  #include "glog/logging.h"
  #include "gtest/gtest.h"
  
  namespace frc971 {
  namespace testing {
  
  // Handles setting up the environment that all control loops need to actually
  // run.
  // This includes sending the queue messages and Clear()ing the queues when
  // appropriate.
  // It also includes dealing with ::aos::time.
  template <typename TestBaseClass>
  class ControlLoopTestTemplated : public TestBaseClass {
   public:
    ControlLoopTestTemplated(
        aos::FlatbufferDetachedBuffer<aos::Configuration> configuration,
        ::std::chrono::nanoseconds dt = kTimeTick)
        : configuration_(std::move(configuration)),
          event_loop_factory_(&configuration_.message()),
          dt_(dt),
          robot_status_event_loop_(MakeEventLoop(
              "robot_status",
              aos::configuration::MultiNode(event_loop_factory_.configuration())
                  ? aos::configuration::GetNode(
                        event_loop_factory_.configuration(), "roborio")
                  : nullptr)) {
      aos::testing::EnableTestLogging();
      robot_state_sender_ =
          robot_status_event_loop_->MakeSender<::aos::RobotState>("/aos");
      joystick_state_sender_ =
          robot_status_event_loop_->MakeSender<::aos::JoystickState>("/aos");
  
      // Schedule the robot status send 1 nanosecond before the loop runs.
      send_robot_state_phased_loop_ = robot_status_event_loop_->AddPhasedLoop(
          [this](int) { SendRobotState(); }, dt_,
          dt - ::std::chrono::nanoseconds(1));
  
      send_joystick_state_timer_ =
          robot_status_event_loop_->AddTimer([this]() { SendJoystickState(); });
  
      robot_status_event_loop_->OnRun([this]() {
        send_joystick_state_timer_->Setup(
            robot_status_event_loop_->monotonic_now(), dt_);
      });
    }
    virtual ~ControlLoopTestTemplated() {}
  
    void set_team_id(uint16_t team_id) { team_id_ = team_id; }
    uint16_t team_id() const { return team_id_; }
  
    // Sets the enabled/disabled bit and (potentially) rebroadcasts the robot
    // state messages.
    void SetEnabled(bool enabled) {
      if (enabled_ != enabled) {
        enabled_ = enabled;
        SendJoystickState();
        SendRobotState();
        send_joystick_state_timer_->Setup(
            robot_status_event_loop_->monotonic_now(), dt_);
      }
    }
  
    // Simulate a reset of the process reading sensors, which tells loops that all
    // index counts etc will be reset.
    void SimulateSensorReset() { ++reader_pid_; }
  
    // Sets the battery voltage in robot_state.
    void set_battery_voltage(double battery_voltage) {
      battery_voltage_ = battery_voltage;
    }
  
    ::std::unique_ptr<::aos::EventLoop> MakeEventLoop(
        std::string_view name, const aos::Node *node = nullptr) {
      return event_loop_factory_.MakeEventLoop(name, node);
    }
  
    void set_send_delay(std::chrono::nanoseconds send_delay) {
      event_loop_factory_.set_send_delay(send_delay);
    }
  
    void RunFor(aos::monotonic_clock::duration duration) {
      event_loop_factory_.RunFor(duration);
    }
  
    ::aos::monotonic_clock::time_point monotonic_now() {
      return robot_status_event_loop_->monotonic_now();
    }
  
    ::std::chrono::nanoseconds dt() const { return dt_; }
  
    const aos::Configuration *configuration() const {
      return &configuration_.message();
    }
  
    aos::SimulatedEventLoopFactory *event_loop_factory() {
      return &event_loop_factory_;
    }
  
   private:
    // Sends out all of the required queue messages.
    void SendJoystickState() {
      if (monotonic_now() >= kDSPacketTime + last_ds_time_ ||
          last_enabled_ != enabled_) {
        auto new_state = joystick_state_sender_.MakeBuilder();
        ::aos::JoystickState::Builder builder =
            new_state.template MakeBuilder<::aos::JoystickState>();
  
        builder.add_fake(true);
  
        builder.add_enabled(enabled_);
        builder.add_autonomous(false);
        builder.add_team_id(team_id_);
  
        CHECK_EQ(new_state.Send(builder.Finish()),
                 aos::RawSender::Error::kOk);
  
        last_ds_time_ = monotonic_now();
        last_enabled_ = enabled_;
      }
    }
  
    bool last_enabled_ = false;
  
    void SendRobotState() {
      auto new_state = robot_state_sender_.MakeBuilder();
  
      ::aos::RobotState::Builder builder =
          new_state.template MakeBuilder<::aos::RobotState>();
  
      builder.add_reader_pid(reader_pid_);
      builder.add_outputs_enabled(enabled_);
      builder.add_browned_out(false);
  
      builder.add_is_3v3_active(true);
      builder.add_is_5v_active(true);
      builder.add_voltage_3v3(3.3);
      builder.add_voltage_5v(5.0);
  
      builder.add_voltage_roborio_in(battery_voltage_);
      builder.add_voltage_battery(battery_voltage_);
  
      new_state.CheckOk(new_state.Send(builder.Finish()));
    }
  
    static constexpr ::std::chrono::microseconds kTimeTick{5000};
    static constexpr ::std::chrono::milliseconds kDSPacketTime{20};
  
    aos::FlatbufferDetachedBuffer<aos::Configuration> configuration_;
  
    aos::SimulatedEventLoopFactory event_loop_factory_;
  
    const ::std::chrono::nanoseconds dt_;
  
    uint16_t team_id_ = 971;
    int32_t reader_pid_ = 1;
    double battery_voltage_ = 12.4;
  
    ::aos::monotonic_clock::time_point last_ds_time_ =
        ::aos::monotonic_clock::min_time;
  
    bool enabled_ = false;
  
    ::std::unique_ptr<::aos::EventLoop> robot_status_event_loop_;
  
    ::aos::Sender<::aos::RobotState> robot_state_sender_;
    ::aos::Sender<::aos::JoystickState> joystick_state_sender_;
  
    ::aos::PhasedLoopHandler *send_robot_state_phased_loop_ = nullptr;
    ::aos::TimerHandler *send_joystick_state_timer_ = nullptr;
  };
  
  typedef ControlLoopTestTemplated<::testing::Test> ControlLoopTest;
  
  template <typename TestBaseClass>
  constexpr ::std::chrono::microseconds
      ControlLoopTestTemplated<TestBaseClass>::kTimeTick;
  
  template <typename TestBaseClass>
  constexpr ::std::chrono::milliseconds
      ControlLoopTestTemplated<TestBaseClass>::kDSPacketTime;
  
  }  // namespace testing
  }  // namespace frc971
  
  #endif  // AOS_CONTROLS_CONTROL_LOOP_TEST_H_
  