| // Copyright 2014 The Chromium 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 <stddef.h> |
| |
| #include <cstdio> |
| #include <unordered_map> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/json/json_reader.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/time/time.h" |
| #include "components/viz/service/debugger/viz_debugger.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/gfx/geometry/vector2d_f.h" |
| |
| #if VIZ_DEBUGGER_IS_ON() |
| using testing::_; |
| using testing::Mock; |
| |
| namespace viz { |
| |
| class VizDebuggerInternal : public VizDebugger { |
| public: |
| void ForceEnabled() { enabled_ = true; } |
| bool Reset(); |
| |
| int GetSourceCount() { return static_cast<int>(sources_.size()); } |
| |
| using VizDebugger::CallSubmitCommon; |
| using VizDebugger::common_lock_; |
| using VizDebugger::DrawCall; |
| using VizDebugger::DrawTextCall; |
| using VizDebugger::FrameAsJson; |
| using VizDebugger::LogCall; |
| using VizDebugger::UpdateFilters; |
| }; |
| |
| bool VizDebuggerInternal::Reset() { |
| submission_count_ = 0; |
| draw_rect_calls_.clear(); |
| draw_text_calls_.clear(); |
| logs_.clear(); |
| last_sent_source_count_ = 0; |
| sources_.clear(); |
| return true; |
| } |
| |
| namespace { |
| |
| struct TestFilter { |
| std::string anno; |
| std::string func; |
| std::string file; |
| bool active = true; |
| bool enabled = true; |
| }; |
| |
| static_assert(sizeof(VizDebuggerInternal) == sizeof(VizDebugger), |
| "This test code exposes the internals of |VizDebugger| via an " |
| "upcast; thus they must be the same size."); |
| |
| class VisualDebuggerTest : public testing::Test { |
| protected: |
| VizDebuggerInternal* GetInternal() { |
| return static_cast<VizDebuggerInternal*>(VizDebugger::GetInstance()); |
| } |
| |
| void SetUp() override { GetInternal()->Reset(); } |
| void TearDown() override { GetInternal()->Reset(); } |
| |
| void SetFilter(std::vector<TestFilter> filters) { |
| base::DictionaryValue filters_json; |
| base::ListValue filters_list; |
| for (auto&& each : filters) { |
| base::DictionaryValue full_filter; |
| base::DictionaryValue selector; |
| if (!each.file.empty()) |
| selector.SetString("file", each.file); |
| |
| if (!each.func.empty()) |
| selector.SetString("func", each.func); |
| |
| selector.SetString("anno", each.anno); |
| |
| full_filter.SetKey("selector", std::move(selector)); |
| full_filter.SetBoolean("active", each.active); |
| full_filter.SetBoolean("enabled", each.enabled); |
| filters_list.Append(std::move(full_filter)); |
| } |
| filters_json.SetKey("filters", std::move(filters_list)); |
| GetInternal()->FilterDebugStream(std::move(filters_json)); |
| GetInternal()->common_lock_.Acquire(); |
| GetInternal()->UpdateFilters(); |
| GetInternal()->common_lock_.Release(); |
| } |
| |
| public: |
| struct StaticSource { |
| std::string file; |
| std::string func; |
| std::string anno; |
| int line; |
| int index; |
| }; |
| |
| void GetFrameData() { |
| sources_.clear(); |
| draw_calls_.clear(); |
| log_calls_.clear(); |
| draw_text_calls_.clear(); |
| GetInternal()->common_lock_.Acquire(); |
| absl::optional<base::Value> global_dict = GetInternal()->FrameAsJson( |
| frame_counter_, gfx::Size(window_x_, window_y_), base::TimeTicks()); |
| GetInternal()->common_lock_.Release(); |
| frame_counter_++; |
| |
| EXPECT_TRUE(global_dict->is_dict()); |
| |
| std::string str; |
| global_dict->FindKey("frame")->GetAsString(&str); |
| base::StringToUint64(str.c_str(), &counter_); |
| static const int kNoVal = -1; |
| int expected_version = |
| global_dict->FindKey("version")->GetIfInt().value_or(kNoVal); |
| // Check to update these unit tests if a backwards compatible change has |
| // been made. |
| EXPECT_EQ(1, expected_version); |
| |
| window_x_ = global_dict->FindKey("windowx")->GetIfInt().value_or(kNoVal); |
| window_y_ = global_dict->FindKey("windowy")->GetIfInt().value_or(kNoVal); |
| |
| base::Value* list_source = global_dict->FindListKey("new_sources"); |
| EXPECT_TRUE(list_source->is_list()); |
| |
| for (size_t i = 0; i < list_source->GetList().size(); i++) { |
| auto&& local_dict = list_source->GetList()[i]; |
| StaticSource ss; |
| local_dict.FindKey("file")->GetAsString(&str); |
| ss.file = str; |
| |
| local_dict.FindKey("func")->GetAsString(&str); |
| ss.func = str; |
| |
| local_dict.FindKey("anno")->GetAsString(&str); |
| ss.anno = str; |
| |
| ss.line = local_dict.FindKey("line")->GetIfInt().value_or(kNoVal); |
| ss.index = local_dict.FindKey("index")->GetIfInt().value_or(kNoVal); |
| sources_.push_back(ss); |
| } |
| |
| base::Value* draw_call_list = global_dict->FindListKey("drawcalls"); |
| EXPECT_TRUE(draw_call_list->is_list()); |
| |
| auto func_common_call = [](const base::Value& dict, int* draw_index, |
| int* source_index, |
| VizDebugger::DrawOption* option) { |
| *draw_index = dict.FindKey("drawindex")->GetIfInt().value_or(kNoVal); |
| *source_index = dict.FindKey("source_index")->GetIfInt().value_or(kNoVal); |
| |
| std::string str; |
| const base::Value* option_dict = dict.FindDictKey("option"); |
| option_dict->FindKey("color")->GetAsString(&str); |
| |
| uint32_t red; |
| uint32_t green; |
| uint32_t blue; |
| std::sscanf(str.c_str(), "#%x%x%x", &red, &green, &blue); |
| |
| option->color_r = red; |
| option->color_g = green; |
| option->color_b = blue; |
| option->color_a = static_cast<uint8_t>( |
| option_dict->FindKey("alpha")->GetIfInt().value_or(kNoVal)); |
| }; |
| |
| for (size_t i = 0; i < draw_call_list->GetList().size(); i++) { |
| const base::Value& local_dict = draw_call_list->GetList()[i]; |
| int draw_index; |
| int source_index; |
| VizDebugger::DrawOption option; |
| func_common_call(local_dict, &draw_index, &source_index, &option); |
| |
| const base::Value* list_size = local_dict.FindListKey("size"); |
| EXPECT_TRUE(list_size->is_list()); |
| int size_x = list_size->GetList()[0].GetIfInt().value_or(kNoVal); |
| int size_y = list_size->GetList()[1].GetIfInt().value_or(kNoVal); |
| |
| const base::Value* list_pos = local_dict.FindListKey("pos"); |
| EXPECT_TRUE(list_pos->is_list()); |
| float pos_x = list_pos->GetList()[0].GetIfDouble().value_or(kNoVal); |
| float pos_y = list_pos->GetList()[1].GetIfDouble().value_or(kNoVal); |
| |
| VizDebuggerInternal::DrawCall draw_call(draw_index, source_index, option, |
| gfx::Size(size_x, size_y), |
| gfx::Vector2dF(pos_x, pos_y)); |
| |
| draw_calls_.push_back(draw_call); |
| } |
| |
| base::Value* text_call_list = global_dict->FindListKey("text"); |
| EXPECT_TRUE(text_call_list->is_list()); |
| |
| for (size_t i = 0; i < text_call_list->GetList().size(); i++) { |
| const base::Value& local_dict = text_call_list->GetList()[i]; |
| int draw_index; |
| int source_index; |
| VizDebugger::DrawOption option; |
| |
| func_common_call(local_dict, &draw_index, &source_index, &option); |
| |
| local_dict.FindKey("text")->GetAsString(&str); |
| std::string text_str = str; |
| |
| const base::Value* list_pos = local_dict.FindListKey("pos"); |
| EXPECT_TRUE(list_pos->is_list()); |
| float pos_x = list_pos->GetList()[0].GetIfDouble().value_or(kNoVal); |
| float pos_y = list_pos->GetList()[1].GetIfDouble().value_or(kNoVal); |
| |
| VizDebuggerInternal::DrawTextCall text_call( |
| draw_index, source_index, option, gfx::Vector2dF(pos_x, pos_y), |
| text_str); |
| |
| draw_text_calls_.push_back(text_call); |
| } |
| |
| base::Value* log_call_list = global_dict->FindListKey("logs"); |
| EXPECT_TRUE(log_call_list->is_list()); |
| |
| for (size_t i = 0; i < log_call_list->GetList().size(); i++) { |
| const base::Value& local_dict = log_call_list->GetList()[i]; |
| int draw_index; |
| int source_index; |
| VizDebugger::DrawOption option; |
| func_common_call(local_dict, &draw_index, &source_index, &option); |
| |
| local_dict.FindKey("value")->GetAsString(&str); |
| std::string log_str = str; |
| |
| VizDebuggerInternal::LogCall log_call(draw_index, source_index, option, |
| log_str); |
| |
| log_calls_.push_back(log_call); |
| } |
| } |
| |
| uint64_t frame_counter_ = 0; |
| |
| // Cached result of call to 'GetFrameData' to simplify code. |
| uint64_t counter_; |
| int window_x_ = 256; |
| int window_y_ = 256; |
| std::vector<StaticSource> sources_; |
| std::vector<VizDebuggerInternal::DrawCall> draw_calls_; |
| std::vector<VizDebuggerInternal::LogCall> log_calls_; |
| std::vector<VizDebuggerInternal::DrawTextCall> draw_text_calls_; |
| }; |
| |
| TEST_F(VisualDebuggerTest, GeneralDrawSubmission) { |
| const char kAnnoRect[] = "annorect"; |
| const char kAnnoText[] = "annotext"; |
| const char kAnnoLog[] = "annolog"; |
| const gfx::Rect kTestRect = gfx::Rect(12, 34, 56, 78); |
| static const int kNumFrames = 4; |
| GetInternal()->ForceEnabled(); |
| for (uint64_t frame_idx = 0; frame_idx < kNumFrames; frame_idx++) { |
| SetFilter({TestFilter({""})}); |
| |
| static const int kNumSubmission = 8; |
| for (int i = 0; i < kNumSubmission; i++) { |
| DBG_DRAW_RECT(kAnnoRect, kTestRect); |
| DBG_DRAW_TEXT(kAnnoText, kTestRect.origin(), |
| base::StringPrintf("Text %d", i)); |
| DBG_LOG(kAnnoLog, "%d", i); |
| } |
| |
| GetFrameData(); |
| |
| EXPECT_EQ(counter_, frame_idx); |
| EXPECT_EQ(window_x_, 256); |
| EXPECT_EQ(window_x_, 256); |
| EXPECT_EQ(draw_calls_.size(), static_cast<size_t>(kNumSubmission)); |
| EXPECT_EQ(log_calls_.size(), static_cast<size_t>(kNumSubmission)); |
| EXPECT_EQ(draw_text_calls_.size(), static_cast<size_t>(kNumSubmission)); |
| |
| if (frame_idx == 0) { |
| EXPECT_EQ(sources_.size(), 3u); |
| EXPECT_EQ(sources_[0].func, "TestBody"); |
| EXPECT_EQ(sources_[0].file, __FILE__); |
| EXPECT_EQ(sources_[0].anno, kAnnoRect); |
| EXPECT_EQ(sources_[1].func, "TestBody"); |
| EXPECT_EQ(sources_[1].file, __FILE__); |
| EXPECT_EQ(sources_[1].anno, kAnnoText); |
| EXPECT_EQ(sources_[2].func, "TestBody"); |
| EXPECT_EQ(sources_[2].file, __FILE__); |
| EXPECT_EQ(sources_[2].anno, kAnnoLog); |
| } else { |
| // After the first frame there are no new sources in the loop. |
| EXPECT_EQ(sources_.size(), 0u); |
| } |
| |
| for (int i = 0; i < kNumSubmission; i++) { |
| EXPECT_EQ(draw_calls_[i].pos, |
| gfx::Vector2dF(kTestRect.origin().x(), kTestRect.origin().y())); |
| EXPECT_EQ(draw_calls_[i].obj_size, kTestRect.size()); |
| EXPECT_EQ(draw_calls_[i].source_index, 0); |
| EXPECT_EQ(draw_calls_[i].draw_index, i * 3); |
| |
| EXPECT_EQ(draw_text_calls_[i].pos, |
| gfx::Vector2dF(kTestRect.origin().x(), kTestRect.origin().y())); |
| EXPECT_EQ(draw_text_calls_[i].source_index, 1); |
| EXPECT_EQ(draw_text_calls_[i].draw_index, i * 3 + 1); |
| EXPECT_EQ(draw_text_calls_[i].text, base::StringPrintf("Text %d", i)); |
| |
| EXPECT_EQ(log_calls_[i].value, base::StringPrintf("%d", i)); |
| EXPECT_EQ(log_calls_[i].source_index, 2); |
| EXPECT_EQ(log_calls_[i].draw_index, i * 3 + 2); |
| } |
| } |
| } |
| |
| static void FunctionNameTest(const char* anno_rect, gfx::Rect rect) { |
| DBG_DRAW_RECT(anno_rect, rect); |
| } |
| |
| TEST_F(VisualDebuggerTest, FilterDrawSubmission) { |
| const char kAnnoRect[] = "annorect"; |
| const char kAnnoMissing[] = "testmissing"; |
| const char kAnnoMatch[] = "before_annorect_after"; |
| |
| GetInternal()->ForceEnabled(); |
| const gfx::Rect kTestRect = gfx::Rect(10, 30, 50, 70); |
| const gfx::Rect kMissingRect = gfx::Rect(11, 33, 55, 77); |
| std::vector<int> valid_indices; |
| SetFilter({TestFilter({"annorect"})}); |
| valid_indices.push_back(GetInternal()->GetSourceCount()); |
| FunctionNameTest(kAnnoRect, kTestRect); |
| valid_indices.push_back(GetInternal()->GetSourceCount()); |
| DBG_DRAW_RECT(kAnnoRect, kTestRect); |
| DBG_DRAW_RECT(kAnnoMissing, kMissingRect); |
| valid_indices.push_back(GetInternal()->GetSourceCount()); |
| DBG_DRAW_RECT(kAnnoMatch, kTestRect); |
| |
| SetFilter({TestFilter({"", "FunctionNameTest"})}); |
| DBG_DRAW_RECT(kAnnoRect, kMissingRect); |
| valid_indices.push_back(0); |
| FunctionNameTest(kAnnoRect, kTestRect); |
| |
| SetFilter({TestFilter({"", "TestBody"})}); |
| FunctionNameTest(kAnnoRect, kMissingRect); |
| valid_indices.push_back(GetInternal()->GetSourceCount()); |
| DBG_DRAW_RECT(kAnnoRect, kTestRect); |
| |
| SetFilter({TestFilter({"", "", "no_file"})}); |
| DBG_DRAW_RECT(kAnnoRect, kMissingRect); |
| |
| SetFilter({TestFilter({"", "", __FILE__})}); |
| valid_indices.push_back(GetInternal()->GetSourceCount()); |
| DBG_DRAW_RECT(kAnnoRect, kTestRect); |
| |
| GetFrameData(); |
| EXPECT_EQ(sources_[0].func, "FunctionNameTest"); |
| EXPECT_EQ(sources_[0].file, __FILE__); |
| EXPECT_EQ(sources_[0].anno, kAnnoRect); |
| EXPECT_EQ(sources_[1].func, "TestBody"); |
| EXPECT_EQ(sources_[1].file, __FILE__); |
| EXPECT_EQ(sources_[1].anno, kAnnoRect); |
| EXPECT_EQ(sources_[2].anno, kAnnoMissing); |
| EXPECT_EQ(sources_[3].anno, kAnnoMatch); |
| |
| auto check_draw = [](const VizDebuggerInternal::DrawCall& draw_call, |
| const gfx::Rect& rect, int src_idx, int draw_idx) { |
| EXPECT_EQ(draw_call.pos, |
| gfx::Vector2dF(rect.origin().x(), rect.origin().y())); |
| EXPECT_EQ(draw_call.obj_size, rect.size()); |
| EXPECT_EQ(draw_call.source_index, src_idx); |
| EXPECT_EQ(draw_call.draw_index, draw_idx); |
| }; |
| |
| // Makes sure all valid indices are here and have the correct rect. |
| for (size_t i = 0; i < draw_calls_.size(); i++) { |
| check_draw(draw_calls_[i], kTestRect, valid_indices[i], i); |
| } |
| } |
| |
| constexpr const char kTestFlagFunctionAnnoName[] = "testflagfunctionanno"; |
| |
| DBG_FLAG_FBOOL(kTestFlagFunctionAnnoName, check_flag_enabled) |
| |
| static bool FlagFunctionTestEnable() { |
| return check_flag_enabled(); |
| } |
| |
| TEST_F(VisualDebuggerTest, TestDebugFlagAnnoAndFunction) { |
| GetInternal()->ForceEnabled(); |
| |
| // Set our test flag to be disabled. |
| SetFilter({TestFilter({kTestFlagFunctionAnnoName, "", "", true, false})}); |
| EXPECT_FALSE(FlagFunctionTestEnable()); |
| SetFilter({TestFilter({kTestFlagFunctionAnnoName, "", "", true, true})}); |
| EXPECT_TRUE(FlagFunctionTestEnable()); |
| SetFilter({TestFilter({kTestFlagFunctionAnnoName, "", "", true, false})}); |
| EXPECT_FALSE(FlagFunctionTestEnable()); |
| } |
| |
| } // namespace |
| } // namespace viz |
| #endif // VIZ_DEBUGGER_IS_ON() |