chromium/ui/display/manager/update_display_configuration_task_unittest.cc

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "ui/display/manager/update_display_configuration_task.h"

#include <stddef.h>

#include <utility>

#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/display/manager/display_layout_manager.h"
#include "ui/display/manager/test/action_logger_util.h"
#include "ui/display/manager/test/fake_display_snapshot.h"
#include "ui/display/manager/test/test_native_display_delegate.h"
#include "ui/display/types/display_constants.h"

namespace display::test {

namespace {

// Non-zero generic connector IDs.
constexpr uint64_t kEdpConnectorId = 71u;
constexpr uint64_t kSecondConnectorId = kEdpConnectorId + 10u;

class TestSoftwareMirroringController
    : public DisplayConfigurator::SoftwareMirroringController {
 public:
  TestSoftwareMirroringController() : is_enabled_(false) {}

  TestSoftwareMirroringController(const TestSoftwareMirroringController&) =
      delete;
  TestSoftwareMirroringController& operator=(
      const TestSoftwareMirroringController&) = delete;

  ~TestSoftwareMirroringController() override = default;

  // DisplayConfigurator::SoftwareMirroringController:
  void SetSoftwareMirroring(bool enabled) override { is_enabled_ = enabled; }
  bool SoftwareMirroringEnabled() const override { return is_enabled_; }
  bool IsSoftwareMirroringEnforced() const override { return false; }

 private:
  bool is_enabled_;
};

class TestDisplayLayoutManager : public DisplayLayoutManager {
 public:
  TestDisplayLayoutManager()
      : should_mirror_(true),
        display_state_(MULTIPLE_DISPLAY_STATE_INVALID),
        power_state_(chromeos::DISPLAY_POWER_ALL_ON) {}

  TestDisplayLayoutManager(const TestDisplayLayoutManager&) = delete;
  TestDisplayLayoutManager& operator=(const TestDisplayLayoutManager&) = delete;

  ~TestDisplayLayoutManager() override {}

  void set_should_mirror(bool should_mirror) { should_mirror_ = should_mirror; }

  void set_display_state(MultipleDisplayState state) { display_state_ = state; }

  void set_power_state(chromeos::DisplayPowerState state) {
    power_state_ = state;
  }

  void set_software_mirroring_controller(
      std::unique_ptr<DisplayConfigurator::SoftwareMirroringController>
          software_mirroring_controller) {
    software_mirroring_controller_ = std::move(software_mirroring_controller);
  }

  // DisplayConfigurator::DisplayLayoutManager:
  DisplayConfigurator::SoftwareMirroringController*
  GetSoftwareMirroringController() const override {
    return software_mirroring_controller_.get();
  }

  DisplayConfigurator::StateController* GetStateController() const override {
    return nullptr;
  }

  MultipleDisplayState GetDisplayState() const override {
    return display_state_;
  }

  chromeos::DisplayPowerState GetPowerState() const override {
    return power_state_;
  }

  bool GetDisplayLayout(
      const std::vector<raw_ptr<DisplaySnapshot, VectorExperimental>>& displays,
      MultipleDisplayState new_display_state,
      chromeos::DisplayPowerState new_power_state,
      const base::flat_set<int64_t>& new_vrr_state,
      std::vector<DisplayConfigureRequest>* requests) const override {
    gfx::Point origin;
    for (DisplaySnapshot* display : displays) {
      const DisplayMode* mode = display->native_mode();
      if (new_display_state == MULTIPLE_DISPLAY_STATE_MULTI_MIRROR)
        mode = should_mirror_ ? FindMirrorMode(displays) : nullptr;

      if (!mode)
        return false;

      const DisplayMode* request_mode =
          new_power_state == chromeos::DISPLAY_POWER_ALL_ON ? mode : nullptr;
      bool request_vrr_state = new_vrr_state.contains(display->display_id()) &&
                               display->IsVrrCapable();
      requests->push_back(DisplayConfigureRequest(display, request_mode, origin,
                                                  request_vrr_state));

      if (new_display_state != MULTIPLE_DISPLAY_STATE_MULTI_MIRROR)
        origin.Offset(0, mode->size().height());
    }

    return true;
  }

  DisplayConfigurator::DisplayStateList GetDisplayStates() const override {
    NOTREACHED_IN_MIGRATION();
    return DisplayConfigurator::DisplayStateList();
  }

  bool IsMirroring() const override {
    return display_state_ == MULTIPLE_DISPLAY_STATE_MULTI_MIRROR;
  }

 private:
  const DisplayMode* FindMirrorMode(
      const std::vector<raw_ptr<DisplaySnapshot, VectorExperimental>>& displays)
      const {
    const DisplayMode* mode = displays[0]->native_mode();
    for (DisplaySnapshot* display : displays) {
      if (mode->size().GetArea() > display->native_mode()->size().GetArea())
        mode = display->native_mode();
    }

    return mode;
  }

  bool should_mirror_;

  MultipleDisplayState display_state_;

  chromeos::DisplayPowerState power_state_;

  std::unique_ptr<DisplayConfigurator::SoftwareMirroringController>
      software_mirroring_controller_;
};

class UpdateDisplayConfigurationTaskTest : public testing::Test {
 public:
  UpdateDisplayConfigurationTaskTest()
      : delegate_(&log_),
        small_mode_({1366, 768}, false, 60.0f),
        big_mode_({2560, 1600}, false, 60.0f, 40.0f) {
    displays_[0] =
        FakeDisplaySnapshot::Builder()
            .SetId(123)
            .SetNativeMode(small_mode_.Clone())
            .SetCurrentMode(small_mode_.Clone())
            .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
            .SetBaseConnectorId(kEdpConnectorId)
            .SetVariableRefreshRateState(VariableRefreshRateState::kVrrDisabled)
            .Build();

    displays_[1] = FakeDisplaySnapshot::Builder()
                       .SetId(456)
                       .SetNativeMode(big_mode_.Clone())
                       .SetCurrentMode(big_mode_.Clone())
                       .SetType(DISPLAY_CONNECTION_TYPE_DISPLAYPORT)
                       .AddMode(small_mode_.Clone())
                       .SetBaseConnectorId(kSecondConnectorId)
                       .SetVariableRefreshRateState(
                           VariableRefreshRateState::kVrrNotCapable)
                       .Build();
  }

  UpdateDisplayConfigurationTaskTest(
      const UpdateDisplayConfigurationTaskTest&) = delete;
  UpdateDisplayConfigurationTaskTest& operator=(
      const UpdateDisplayConfigurationTaskTest&) = delete;

  ~UpdateDisplayConfigurationTaskTest() override = default;

  void UpdateDisplays(size_t count) {
    std::vector<std::unique_ptr<DisplaySnapshot>> displays;
    for (size_t i = 0; i < count; ++i)
      displays.push_back(displays_[i]->Clone());

    delegate_.SetOutputs(std::move(displays));
  }

  void ResponseCallback(
      bool success,
      const std::vector<raw_ptr<DisplaySnapshot, VectorExperimental>>& displays,
      const std::vector<raw_ptr<DisplaySnapshot, VectorExperimental>>&
          unassociated_displays,
      MultipleDisplayState new_display_state,
      chromeos::DisplayPowerState new_power_state) {
    configured_ = true;
    configuration_status_ = success;
    display_states_ = displays;
    display_state_ = new_display_state;
    power_state_ = new_power_state;

    if (success) {
      layout_manager_.set_display_state(display_state_);
      layout_manager_.set_power_state(power_state_);
    }
  }

 protected:
  ActionLogger log_;
  TestNativeDisplayDelegate delegate_;
  TestDisplayLayoutManager layout_manager_;

  const DisplayMode small_mode_;
  const DisplayMode big_mode_;

  std::unique_ptr<DisplaySnapshot> displays_[2];

  bool configured_ = false;
  bool configuration_status_ = false;
  std::vector<raw_ptr<DisplaySnapshot, VectorExperimental>> display_states_;
  MultipleDisplayState display_state_ = MULTIPLE_DISPLAY_STATE_INVALID;
  chromeos::DisplayPowerState power_state_ = chromeos::DISPLAY_POWER_ALL_ON;
};

}  // namespace

TEST_F(UpdateDisplayConfigurationTaskTest, HeadlessConfiguration) {
  {
    UpdateDisplayConfigurationTask task(
        &delegate_, &layout_manager_, MULTIPLE_DISPLAY_STATE_HEADLESS,
        chromeos::DISPLAY_POWER_ALL_ON,
        DisplayConfigurator::kSetDisplayPowerNoFlags,
        /*new_vrr_state=*/{}, /*refresh_rate_overrides=*/{},
        /*force_configure=*/false, kConfigurationTypeFull,
        base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                       base::Unretained(this)));
    task.Run();
  }

  EXPECT_TRUE(configured_);
  EXPECT_TRUE(configuration_status_);
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_HEADLESS, display_state_);
  EXPECT_EQ(chromeos::DISPLAY_POWER_ALL_ON, power_state_);
  EXPECT_EQ(kNoActions, log_.GetActionsAndClear());
}

TEST_F(UpdateDisplayConfigurationTaskTest, SingleConfiguration) {
  UpdateDisplays(1);

  {
    UpdateDisplayConfigurationTask task(
        &delegate_, &layout_manager_, MULTIPLE_DISPLAY_STATE_SINGLE,
        chromeos::DISPLAY_POWER_ALL_ON,
        DisplayConfigurator::kSetDisplayPowerNoFlags,
        /*new_vrr_state=*/{}, /*refresh_rate_overrides=*/{},
        /*force_configure=*/false, kConfigurationTypeFull,
        base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                       base::Unretained(this)));
    task.Run();
  }

  EXPECT_TRUE(configured_);
  EXPECT_TRUE(configuration_status_);
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_SINGLE, display_state_);
  EXPECT_EQ(chromeos::DISPLAY_POWER_ALL_ON, power_state_);
  EXPECT_EQ(JoinActions(kTestModesetStr,
                        GetCrtcAction({displays_[0]->display_id(), gfx::Point(),
                                       &small_mode_})
                            .c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        GetCrtcAction({displays_[0]->display_id(), gfx::Point(),
                                       &small_mode_})
                            .c_str(),
                        kModesetOutcomeSuccess, nullptr),
            log_.GetActionsAndClear());
}

TEST_F(UpdateDisplayConfigurationTaskTest, ExtendedConfiguration) {
  UpdateDisplays(2);

  {
    UpdateDisplayConfigurationTask task(
        &delegate_, &layout_manager_, MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED,
        chromeos::DISPLAY_POWER_ALL_ON,
        DisplayConfigurator::kSetDisplayPowerNoFlags,
        /*new_vrr_state=*/{}, /*refresh_rate_overrides=*/{},
        /*force_configure=*/false, kConfigurationTypeFull,
        base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                       base::Unretained(this)));
    task.Run();
  }

  EXPECT_TRUE(configured_);
  EXPECT_TRUE(configuration_status_);
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED, display_state_);
  EXPECT_EQ(chromeos::DISPLAY_POWER_ALL_ON, power_state_);
  EXPECT_EQ(
      JoinActions(kTestModesetStr,
                  GetCrtcAction(
                      {displays_[0]->display_id(), gfx::Point(), &small_mode_})
                      .c_str(),
                  GetCrtcAction({displays_[1]->display_id(),
                                 gfx::Point(0, small_mode_.size().height()),
                                 &big_mode_})
                      .c_str(),
                  kModesetOutcomeSuccess, kCommitModesetStr,
                  GetCrtcAction(
                      {displays_[0]->display_id(), gfx::Point(), &small_mode_})
                      .c_str(),
                  GetCrtcAction({displays_[1]->display_id(),
                                 gfx::Point(0, small_mode_.size().height()),
                                 &big_mode_})
                      .c_str(),
                  kModesetOutcomeSuccess, nullptr),
      log_.GetActionsAndClear());
}

TEST_F(UpdateDisplayConfigurationTaskTest, MirrorConfiguration) {
  UpdateDisplays(2);

  {
    UpdateDisplayConfigurationTask task(
        &delegate_, &layout_manager_, MULTIPLE_DISPLAY_STATE_MULTI_MIRROR,
        chromeos::DISPLAY_POWER_ALL_ON,
        DisplayConfigurator::kSetDisplayPowerNoFlags,
        /*new_vrr_state=*/{}, /*refresh_rate_overrides=*/{},
        /*force_configure=*/false, kConfigurationTypeFull,
        base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                       base::Unretained(this)));
    task.Run();
  }

  EXPECT_TRUE(configured_);
  EXPECT_TRUE(configuration_status_);
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_MULTI_MIRROR, display_state_);
  EXPECT_EQ(chromeos::DISPLAY_POWER_ALL_ON, power_state_);
  EXPECT_EQ(JoinActions(kTestModesetStr,
                        GetCrtcAction({displays_[0]->display_id(), gfx::Point(),
                                       &small_mode_})
                            .c_str(),
                        GetCrtcAction({displays_[1]->display_id(), gfx::Point(),
                                       &small_mode_})
                            .c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        GetCrtcAction({displays_[0]->display_id(), gfx::Point(),
                                       &small_mode_})
                            .c_str(),
                        GetCrtcAction({displays_[1]->display_id(), gfx::Point(),
                                       &small_mode_})
                            .c_str(),
                        kModesetOutcomeSuccess, nullptr),
            log_.GetActionsAndClear());
}

TEST_F(UpdateDisplayConfigurationTaskTest, FailMirrorConfiguration) {
  layout_manager_.set_should_mirror(false);
  UpdateDisplays(2);

  {
    UpdateDisplayConfigurationTask task(
        &delegate_, &layout_manager_, MULTIPLE_DISPLAY_STATE_MULTI_MIRROR,
        chromeos::DISPLAY_POWER_ALL_ON,
        DisplayConfigurator::kSetDisplayPowerNoFlags,
        /*new_vrr_state=*/{}, /*refresh_rate_overrides=*/{},
        /*force_configure=*/false, kConfigurationTypeFull,
        base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                       base::Unretained(this)));
    task.Run();
  }

  EXPECT_TRUE(configured_);
  EXPECT_FALSE(configuration_status_);
  EXPECT_EQ(kNoActions, log_.GetActionsAndClear());
}

TEST_F(UpdateDisplayConfigurationTaskTest, FailExtendedConfiguration) {
  delegate_.set_max_configurable_pixels(1);
  UpdateDisplays(2);

  {
    UpdateDisplayConfigurationTask task(
        &delegate_, &layout_manager_, MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED,
        chromeos::DISPLAY_POWER_ALL_ON,
        DisplayConfigurator::kSetDisplayPowerNoFlags,
        /*new_vrr_state=*/{}, /*refresh_rate_overrides=*/{},
        /*force_configure=*/false, kConfigurationTypeFull,
        base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                       base::Unretained(this)));
    task.Run();
  }

  EXPECT_TRUE(configured_);
  EXPECT_FALSE(configuration_status_);
  EXPECT_EQ(
      JoinActions(
          // All displays will fail to modeset together. Initiate retry logic.
          kTestModesetStr,
          GetCrtcAction(
              {displays_[0]->display_id(), gfx::Point(), &small_mode_})
              .c_str(),
          GetCrtcAction({displays_[1]->display_id(),
                         gfx::Point(0, small_mode_.size().height()),
                         &big_mode_})
              .c_str(),
          kModesetOutcomeFailure,
          // We first attempt to modeset the internal display with all
          // other displays disabled, which will fail.
          kTestModesetStr,
          GetCrtcAction(
              {displays_[0]->display_id(), gfx::Point(), &small_mode_})
              .c_str(),
          GetCrtcAction({displays_[1]->display_id(),
                         gfx::Point(0, small_mode_.size().height()), nullptr})
              .c_str(),
          kModesetOutcomeFailure,
          // Since internal displays are restricted to their preferred mode,
          // there are no other modes to try. Disable the internal display when
          // we attempt to modeset displays that are connected to other
          // connectors. Regardless of what happens next, the configuration will
          // still fail completely. External display fail modeset, downgrade
          // once, and then fail completely.
          kTestModesetStr,
          GetCrtcAction({displays_[0]->display_id(), gfx::Point(), nullptr})
              .c_str(),
          GetCrtcAction({displays_[1]->display_id(),
                         gfx::Point(0, small_mode_.size().height()),
                         &big_mode_})
              .c_str(),
          kModesetOutcomeFailure, kTestModesetStr,
          GetCrtcAction({displays_[0]->display_id(), gfx::Point(), nullptr})
              .c_str(),
          GetCrtcAction({displays_[1]->display_id(),
                         gfx::Point(0, small_mode_.size().height()),
                         &small_mode_})
              .c_str(),
          kModesetOutcomeFailure, nullptr),
      log_.GetActionsAndClear());
}

TEST_F(UpdateDisplayConfigurationTaskTest, SingleChangePowerConfiguration) {
  UpdateDisplays(1);

  {
    UpdateDisplayConfigurationTask task(
        &delegate_, &layout_manager_, MULTIPLE_DISPLAY_STATE_SINGLE,
        chromeos::DISPLAY_POWER_ALL_ON,
        DisplayConfigurator::kSetDisplayPowerNoFlags,
        /*new_vrr_state=*/{}, /*refresh_rate_overrides=*/{},
        /*force_configure=*/false, kConfigurationTypeFull,
        base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                       base::Unretained(this)));
    task.Run();
  }

  EXPECT_TRUE(configured_);
  EXPECT_TRUE(configuration_status_);
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_SINGLE, display_state_);
  EXPECT_EQ(chromeos::DISPLAY_POWER_ALL_ON, power_state_);
  EXPECT_EQ(JoinActions(kTestModesetStr,
                        GetCrtcAction({displays_[0]->display_id(), gfx::Point(),
                                       &small_mode_})
                            .c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        GetCrtcAction({displays_[0]->display_id(), gfx::Point(),
                                       &small_mode_})
                            .c_str(),
                        kModesetOutcomeSuccess, nullptr),
            log_.GetActionsAndClear());

  // Turn power off
  {
    UpdateDisplayConfigurationTask task(
        &delegate_, &layout_manager_, MULTIPLE_DISPLAY_STATE_SINGLE,
        chromeos::DISPLAY_POWER_ALL_OFF,
        DisplayConfigurator::kSetDisplayPowerNoFlags,
        /*new_vrr_state=*/{}, /*refresh_rate_overrides=*/{},
        /*force_configure=*/false, kConfigurationTypeFull,
        base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                       base::Unretained(this)));
    task.Run();
  }

  EXPECT_TRUE(configuration_status_);
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_SINGLE, display_state_);
  EXPECT_EQ(chromeos::DISPLAY_POWER_ALL_OFF, power_state_);
  EXPECT_EQ(
      JoinActions(
          kTestModesetStr,
          GetCrtcAction({displays_[0]->display_id(), gfx::Point(), nullptr})
              .c_str(),
          kModesetOutcomeSuccess, kCommitModesetStr,
          GetCrtcAction({displays_[0]->display_id(), gfx::Point(), nullptr})
              .c_str(),
          kModesetOutcomeSuccess, nullptr),
      log_.GetActionsAndClear());
}

TEST_F(UpdateDisplayConfigurationTaskTest, NoopSoftwareMirrorConfiguration) {
  layout_manager_.set_should_mirror(false);
  layout_manager_.set_software_mirroring_controller(
      std::make_unique<TestSoftwareMirroringController>());
  UpdateDisplays(2);

  {
    UpdateDisplayConfigurationTask task(
        &delegate_, &layout_manager_, MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED,
        chromeos::DISPLAY_POWER_ALL_ON,
        DisplayConfigurator::kSetDisplayPowerNoFlags,
        /*new_vrr_state=*/{}, /*refresh_rate_overrides=*/{},
        /*force_configure=*/false, kConfigurationTypeFull,
        base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                       base::Unretained(this)));
    task.Run();
  }

  log_.GetActionsAndClear();

  {
    UpdateDisplayConfigurationTask task(
        &delegate_, &layout_manager_, MULTIPLE_DISPLAY_STATE_MULTI_MIRROR,
        chromeos::DISPLAY_POWER_ALL_ON,
        DisplayConfigurator::kSetDisplayPowerNoFlags,
        /*new_vrr_state=*/{}, /*refresh_rate_overrides=*/{},
        /*force_configure=*/false, kConfigurationTypeFull,
        base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                       base::Unretained(this)));
    task.Run();
  }

  EXPECT_TRUE(configuration_status_);
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED, display_state_);
  EXPECT_TRUE(layout_manager_.GetSoftwareMirroringController()
                  ->SoftwareMirroringEnabled());
  EXPECT_EQ(kNoActions, log_.GetActionsAndClear());
}

TEST_F(UpdateDisplayConfigurationTaskTest,
       ForceConfigurationWhileGoingToSoftwareMirror) {
  layout_manager_.set_should_mirror(false);
  layout_manager_.set_software_mirroring_controller(
      std::make_unique<TestSoftwareMirroringController>());
  UpdateDisplays(2);

  {
    UpdateDisplayConfigurationTask task(
        &delegate_, &layout_manager_, MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED,
        chromeos::DISPLAY_POWER_ALL_ON,
        DisplayConfigurator::kSetDisplayPowerNoFlags,
        /*new_vrr_state=*/{}, /*refresh_rate_overrides=*/{},
        /*force_configure=*/false, kConfigurationTypeFull,
        base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                       base::Unretained(this)));
    task.Run();
  }

  log_.GetActionsAndClear();

  {
    UpdateDisplayConfigurationTask task(
        &delegate_, &layout_manager_, MULTIPLE_DISPLAY_STATE_MULTI_MIRROR,
        chromeos::DISPLAY_POWER_ALL_ON,
        DisplayConfigurator::kSetDisplayPowerNoFlags,
        /*new_vrr_state=*/{}, /*refresh_rate_overrides=*/{},
        /*force_configure=*/true, kConfigurationTypeFull,
        base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                       base::Unretained(this)));
    task.Run();
  }

  EXPECT_TRUE(configuration_status_);
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED, display_state_);
  EXPECT_TRUE(layout_manager_.GetSoftwareMirroringController()
                  ->SoftwareMirroringEnabled());
  EXPECT_EQ(
      JoinActions(kTestModesetStr,
                  GetCrtcAction(
                      {displays_[0]->display_id(), gfx::Point(), &small_mode_})
                      .c_str(),
                  GetCrtcAction({displays_[1]->display_id(),
                                 gfx::Point(0, small_mode_.size().height()),
                                 &big_mode_})
                      .c_str(),
                  kModesetOutcomeSuccess, kCommitModesetStr,
                  GetCrtcAction(
                      {displays_[0]->display_id(), gfx::Point(), &small_mode_})
                      .c_str(),
                  GetCrtcAction({displays_[1]->display_id(),
                                 gfx::Point(0, small_mode_.size().height()),
                                 &big_mode_})
                      .c_str(),
                  kModesetOutcomeSuccess, nullptr),
      log_.GetActionsAndClear());
}

TEST_F(UpdateDisplayConfigurationTaskTest, VrrConfiguration) {
  UpdateDisplays(2);

  // Initial configuration.
  {
    UpdateDisplayConfigurationTask task(
        &delegate_, &layout_manager_, MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED,
        chromeos::DISPLAY_POWER_ALL_ON,
        DisplayConfigurator::kSetDisplayPowerNoFlags,
        /*new_vrr_state=*/{}, /*refresh_rate_overrides=*/{},
        /*force_configure=*/false, kConfigurationTypeFull,
        base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                       base::Unretained(this)));
    task.Run();
  }

  configured_ = false;
  configuration_status_ = false;
  log_.GetActionsAndClear();

  // VRR configuration.
  {
    UpdateDisplayConfigurationTask task(
        &delegate_, &layout_manager_, MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED,
        chromeos::DISPLAY_POWER_ALL_ON,
        DisplayConfigurator::kSetDisplayPowerOnlyIfSingleInternalDisplay,
        /*new_vrr_state=*/
        {displays_[0]->display_id(), displays_[1]->display_id()},
        /*refresh_rate_overrides=*/{},
        /*force_configure=*/false, kConfigurationTypeFull,
        base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                       base::Unretained(this)));
    task.Run();
  }

  EXPECT_TRUE(configured_);
  EXPECT_TRUE(configuration_status_);
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED, display_state_);
  EXPECT_EQ(chromeos::DISPLAY_POWER_ALL_ON, power_state_);
  EXPECT_EQ(
      JoinActions(kTestModesetStr,
                  GetCrtcAction({displays_[0]->display_id(), gfx::Point(),
                                 &small_mode_, /*enable_vrr=*/true})
                      .c_str(),
                  GetCrtcAction({displays_[1]->display_id(),
                                 gfx::Point(0, small_mode_.size().height()),
                                 &big_mode_, /*enable_vrr=*/false})
                      .c_str(),
                  kModesetOutcomeSuccess, kCommitModesetStr,
                  GetCrtcAction({displays_[0]->display_id(), gfx::Point(),
                                 &small_mode_, /*enable_vrr=*/true})
                      .c_str(),
                  GetCrtcAction({displays_[1]->display_id(),
                                 gfx::Point(0, small_mode_.size().height()),
                                 &big_mode_, /*enable_vrr=*/false})
                      .c_str(),
                  kModesetOutcomeSuccess, nullptr),
      log_.GetActionsAndClear());
}

TEST_F(UpdateDisplayConfigurationTaskTest, NoopConfiguration) {
  UpdateDisplays(2);

  // Initial configuration.
  {
    UpdateDisplayConfigurationTask task(
        &delegate_, &layout_manager_, MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED,
        chromeos::DISPLAY_POWER_ALL_ON,
        DisplayConfigurator::kSetDisplayPowerNoFlags,
        /*new_vrr_state=*/{}, /*refresh_rate_overrides=*/{},
        /*force_configure=*/false, kConfigurationTypeFull,
        base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                       base::Unretained(this)));
    task.Run();
  }

  configured_ = false;
  configuration_status_ = false;
  log_.GetActionsAndClear();

  // Noop configuration.
  {
    UpdateDisplayConfigurationTask task(
        &delegate_, &layout_manager_, MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED,
        chromeos::DISPLAY_POWER_ALL_ON,
        DisplayConfigurator::kSetDisplayPowerOnlyIfSingleInternalDisplay,
        /*new_vrr_state=*/{}, /*refresh_rate_overrides=*/{},
        /*force_configure=*/false, kConfigurationTypeFull,
        base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                       base::Unretained(this)));
    task.Run();
  }

  EXPECT_TRUE(configured_);
  EXPECT_TRUE(configuration_status_);
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED, display_state_);
  EXPECT_EQ(chromeos::DISPLAY_POWER_ALL_ON, power_state_);
  EXPECT_EQ(kNoActions, log_.GetActionsAndClear());
}

}  // namespace display::test