1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203
  204
  205
  206
  207
  208
  209
  210
  211
  212
  213
  214

cc / metrics / frame_sorter_unittest.cc [blame]

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

#include "cc/metrics/frame_sorter.h"

#include <string>
#include <utility>
#include <vector>

#include "base/functional/bind.h"
#include "base/strings/string_number_conversions.h"
#include "cc/metrics/frame_info.h"
#include "cc/test/fake_frame_info.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace cc {

// Test class for FrameSorter
class FrameSorterTest : public testing::Test {
 public:
  FrameSorterTest()
      : frame_sorter_(base::BindRepeating(&FrameSorterTest::FlushFrame,
                                          base::Unretained(this))) {
    IncreaseSourceId();
  }
  ~FrameSorterTest() override = default;

  viz::BeginFrameArgs GetNextFrameArgs() {
    uint64_t sequence_number = next_frame_sequence_number_++;
    return viz::BeginFrameArgs::Create(
        BEGINFRAME_FROM_HERE, next_frame_source_id_, sequence_number,
        last_begin_frame_time_,
        last_begin_frame_time_ + viz::BeginFrameArgs::DefaultInterval(),
        viz::BeginFrameArgs::DefaultInterval(), viz::BeginFrameArgs::NORMAL);
  }

  void IncreaseSourceId() {
    next_frame_source_id_++;
    current_frame_id_ = -1;
    args_.clear();
    next_frame_sequence_number_ = viz::BeginFrameArgs::kStartingFrameNumber;
  }

  // Simulates a sequence of queries
  // Possible queries:
  // "S{frame_number}": Start frame.
  // "D{frame_number}": End frame as dropped.
  // "P{frame_number}": End frame as none-dropped (presented).
  // "E{frame_number}": Frame expects twp acks.
  // "I": Increase source_id (simulating a crash).
  // "R": Reset the frame sorter.
  // Method expects the start of frames to be in order starting with 1.
  void SimulateQueries(std::vector<std::string> queries) {
    // Keeps track of how many times a frame is terminated.
    std::map<int, int> end_counters;

    for (auto& query : queries) {
      int id;
      base::StringToInt(query.substr(1), &id);
      if (id > current_frame_id_) {
        current_frame_id_++;
        args_.push_back(GetNextFrameArgs());
      }
      switch (query[0]) {
        case 'S':
          frame_sorter_.AddNewFrame(args_[id]);
          break;
        case 'D': {
          ++end_counters[id];
          FrameInfo info =
              CreateFakeFrameInfo(FrameInfo::FrameFinalState::kDropped);
          if (end_counters[id] == 1) {
            // For the first response to the frame, mark it as not including
            // update from the main-thread.
            info.main_thread_response = FrameInfo::MainThreadResponse::kMissing;
          } else {
            DCHECK_EQ(2, end_counters[id]);
            info.main_thread_response =
                FrameInfo::MainThreadResponse::kIncluded;
          }
          frame_sorter_.AddFrameResult(args_[id], info);
          break;
        }
        case 'P': {
          ++end_counters[id];
          FrameInfo info =
              CreateFakeFrameInfo(FrameInfo::FrameFinalState::kPresentedAll);
          if (end_counters[id] == 1) {
            // For the first response to the frame, mark it as not including
            // update from the main-thread.
            info.main_thread_response = FrameInfo::MainThreadResponse::kMissing;
          } else {
            DCHECK_EQ(2, end_counters[id]);
            info.main_thread_response =
                FrameInfo::MainThreadResponse::kIncluded;
          }
          frame_sorter_.AddFrameResult(args_[id], info);
          break;
        }
        case 'I':
          IncreaseSourceId();
          break;
        case 'R':
          frame_sorter_.Reset();
          break;
      }
    }
  }

  void ValidateResults(
      std::vector<std::pair<uint64_t, bool>> expected_results) {
    EXPECT_EQ(sorted_frames_.size(), expected_results.size());
    int result_index = 0;
    for (auto result : expected_results) {
      auto& sorted_frame = sorted_frames_[result_index];
      EXPECT_EQ(sorted_frame.first.frame_id.sequence_number, result.first + 1);
      EXPECT_EQ(sorted_frame.second, result.second);
      result_index++;
    }
  }

 private:
  void FlushFrame(const viz::BeginFrameArgs& args, const FrameInfo& frame) {
    sorted_frames_.emplace_back(args, frame.IsDroppedAffectingSmoothness());
  }

  FrameSorter frame_sorter_;
  std::vector<std::pair<const viz::BeginFrameArgs, bool>> sorted_frames_;
  base::TimeTicks last_begin_frame_time_ = base::TimeTicks::Now();
  uint64_t next_frame_source_id_ = 0;
  uint64_t next_frame_sequence_number_ =
      viz::BeginFrameArgs::kStartingFrameNumber;
  std::vector<viz::BeginFrameArgs> args_ = {};
  int current_frame_id_ = -1;
};

TEST_F(FrameSorterTest, TestSortingFrames) {
  // Frame end in order of F0, F2, F1, F3, but they should be flushed in the
  // order they began (eg. F0, F1, F2, F3).
  std::vector<std::string> queries = {"S0", "S1", "P0", "S2",
                                      "P2", "S3", "P1", "D3"};
  std::vector<std::pair<uint64_t, bool>> expected_results = {
      {0, false}, {1, false}, {2, false}, {3, true}};

  SimulateQueries(queries);
  ValidateResults(expected_results);
}

TEST_F(FrameSorterTest, ResetInMiddleOfAcks) {
  // Reset occur in between the start and ack of F1 & F3, so except these two
  // all other frames should be flushed in order in the sorted_frames_.
  std::vector<std::string> queries = {"S0", "S1", "P0", "S2", "P2", "S3",
                                      "R",  "S4", "P1", "D4", "D3"};
  std::vector<std::pair<uint64_t, bool>> expected_results = {
      {0, false}, {2, false}, {4, true}};

  SimulateQueries(queries);
  ValidateResults(expected_results);
}

TEST_F(FrameSorterTest, ExpectingMultipleAcks) {
  // F1 has multiple acks and the final ack is received at the end. Which makes
  // other frames to wait in pending tree before being flushed, all in order.
  // Also in any of the duplicated acks report a dropped frame, the overall
  // status for that frame should be dropped.
  std::vector<std::string> queries = {"S0", "S1", "P0", "S1", "S2", "P2",
                                      "S3", "S4", "P1", "D3", "D4", "D1"};
  std::vector<std::pair<uint64_t, bool>> expected_results = {
      {0, false}, {1, true}, {2, false}, {3, true}, {4, true}};

  SimulateQueries(queries);
  ValidateResults(expected_results);
}

TEST_F(FrameSorterTest, ExpectingMultipleAcksWithReset) {
  // Combination of last two tests. Reset occurs in middle of start and ack of
  // F1 and F3. Also F1 expects two acks, which both are received after reset.
  std::vector<std::string> queries = {"S0", "S1", "P0", "S1", "S2", "P2", "S3",
                                      "R",  "S4", "P1", "D1", "D4", "D3"};
  std::vector<std::pair<uint64_t, bool>> expected_results = {
      {0, false}, {2, false}, {4, true}};

  SimulateQueries(queries);
  ValidateResults(expected_results);
}

TEST_F(FrameSorterTest, ExpectingMultipleAcksWithReset2) {
  // Reset occurs in middle of the two starts of F0.
  // This is to test if F1 will be flushed properly while being in between the
  // two acks of F0, which should be ignored as a result of reset.
  std::vector<std::string> queries = {"S0", "R", "S0", "P0", "S1", "D0", "D1"};
  std::vector<std::pair<uint64_t, bool>> expected_results = {{1, true}};

  SimulateQueries(queries);
  ValidateResults(expected_results);
}

TEST_F(FrameSorterTest, ExpectingMultipleAcksWithSourceIdIncrease) {
  // Reset and increase in source_id occurs in middle start and ack of F1.
  // This is to simulate a navigation. So the initial F0 has source_id of 0
  // while the F0 after reset has source_id of 1, and because of that the
  // start and ack for F0 after the reset is not ignored (The ignore is only
  // needed when frame has the same source_id and sequence_number).
  std::vector<std::string> queries = {"S0", "S0", "S1", "S1", "I",  "R",
                                      "S0", "S0", "P0", "S1", "P1", "D0"};
  std::vector<std::pair<uint64_t, bool>> expected_results = {{0, true},
                                                             {1, false}};

  SimulateQueries(queries);
  ValidateResults(expected_results);
}

}  // namespace cc