| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| #include "util/runtime_profile.h" |
| |
| #include <gtest/gtest.h> |
| |
| #include <cstdlib> |
| #include <sstream> |
| |
| #include "common/exception.h" |
| #include "common/object_pool.h" |
| |
| using namespace std; |
| |
| namespace doris { |
| |
| class RuntimeProfileTest : public testing::Test {}; |
| |
| TEST(RuntimeProfileTest, Basic) { |
| RuntimeProfile profile_a("ProfileA"); |
| RuntimeProfile profile_a1("ProfileA1"); |
| RuntimeProfile profile_a2("ProfileA2"); |
| |
| TRuntimeProfileTree thrift_profile; |
| |
| profile_a.add_child(&profile_a1, true); |
| profile_a.add_child(&profile_a2, true); |
| |
| // Test Empty |
| profile_a.to_thrift(&thrift_profile.nodes); |
| EXPECT_EQ(thrift_profile.nodes.size(), 3); |
| thrift_profile.nodes.clear(); |
| |
| RuntimeProfile::Counter* counter_a; |
| RuntimeProfile::Counter* counter_b; |
| RuntimeProfile::Counter* counter_merged; |
| |
| // Updating/setting counter |
| counter_a = profile_a.add_counter("A", TUnit::UNIT); |
| EXPECT_TRUE(counter_a != nullptr); |
| counter_a->update(10); |
| counter_a->update(-5); |
| EXPECT_EQ(counter_a->value(), 5); |
| counter_a->set(1L); |
| EXPECT_EQ(counter_a->value(), 1); |
| |
| counter_b = profile_a2.add_counter("B", TUnit::BYTES); |
| EXPECT_TRUE(counter_b != nullptr); |
| |
| std::stringstream ss; |
| // Serialize/deserialize |
| profile_a.to_thrift(&thrift_profile.nodes, 2); |
| profile_a.pretty_print(&ss, " ", 4); |
| std::cout << "Profile A:\n" << ss.str() << std::endl; |
| ss.clear(); |
| ASSERT_EQ(thrift_profile.nodes.size(), 3); |
| // Reset stream. |
| ss = std::stringstream(); |
| std::unique_ptr<RuntimeProfile> from_thrift = RuntimeProfile::from_thrift(thrift_profile); |
| from_thrift->pretty_print(&ss, "", 4); |
| std::cout << "From thrift profile:\n" << ss.str() << std::endl; |
| ss.clear(); |
| |
| counter_merged = from_thrift->get_counter("A"); |
| ASSERT_NE(counter_merged, nullptr); |
| EXPECT_EQ(counter_merged->value(), 1); |
| EXPECT_TRUE(from_thrift->get_counter("Not there") == nullptr); |
| |
| // merge |
| RuntimeProfile merged_profile("Merged"); |
| merged_profile.merge(from_thrift.get()); |
| counter_merged = merged_profile.get_counter("A"); |
| EXPECT_EQ(counter_merged->value(), 1); |
| |
| // merge 2 more times, counters should get aggregated |
| merged_profile.merge(from_thrift.get()); |
| merged_profile.merge(from_thrift.get()); |
| EXPECT_EQ(counter_merged->value(), 3); |
| |
| // update |
| RuntimeProfile updated_profile("updated"); |
| updated_profile.update(thrift_profile); |
| RuntimeProfile::Counter* counter_updated = updated_profile.get_counter("A"); |
| EXPECT_EQ(counter_updated->value(), 1); |
| |
| // update 2 more times, counters should stay the same |
| updated_profile.update(thrift_profile); |
| updated_profile.update(thrift_profile); |
| EXPECT_EQ(counter_updated->value(), 1); |
| } |
| |
| TEST(RuntimeProfileTest, ProtoBasic) { |
| RuntimeProfile profile_a("ProfileA"); |
| RuntimeProfile profile_a1("ProfileA1"); |
| RuntimeProfile profile_a2("ProfileA2"); |
| |
| profile_a.add_child(&profile_a1, true); |
| profile_a.add_child(&profile_a2, true); |
| |
| PRuntimeProfileTree proto_profile; |
| |
| // Test Empty serialization |
| profile_a.to_proto(&proto_profile); |
| EXPECT_EQ(proto_profile.nodes_size(), 3); |
| proto_profile.clear_nodes(); |
| |
| RuntimeProfile::Counter* counter_a; |
| RuntimeProfile::Counter* counter_b; |
| RuntimeProfile::Counter* counter_merged; |
| |
| // Updating/setting counter |
| counter_a = profile_a.add_counter("A", TUnit::UNIT); |
| EXPECT_TRUE(counter_a != nullptr); |
| counter_a->update(10); |
| counter_a->update(-5); |
| EXPECT_EQ(counter_a->value(), 5); |
| counter_a->set(1L); |
| EXPECT_EQ(counter_a->value(), 1); |
| |
| counter_b = profile_a2.add_counter("B", TUnit::BYTES); |
| EXPECT_TRUE(counter_b != nullptr); |
| |
| std::stringstream ss; |
| // Serialize to proto |
| profile_a.to_proto(&proto_profile, 2); |
| profile_a.pretty_print(&ss, " ", 4); |
| std::cout << "Profile A:\n" << ss.str() << std::endl; |
| ss.str(""); |
| ss.clear(); |
| |
| ASSERT_EQ(proto_profile.nodes_size(), 3); |
| |
| // Deserialize from proto |
| std::unique_ptr<RuntimeProfile> from_proto = RuntimeProfile::from_proto(proto_profile); |
| from_proto->pretty_print(&ss, "", 4); |
| std::cout << "From proto profile:\n" << ss.str() << std::endl; |
| ss.str(""); |
| ss.clear(); |
| |
| counter_merged = from_proto->get_counter("A"); |
| ASSERT_NE(counter_merged, nullptr); |
| EXPECT_EQ(counter_merged->value(), 1); |
| EXPECT_TRUE(from_proto->get_counter("Not there") == nullptr); |
| |
| // merge |
| RuntimeProfile merged_profile("Merged"); |
| merged_profile.merge(from_proto.get()); |
| counter_merged = merged_profile.get_counter("A"); |
| EXPECT_EQ(counter_merged->value(), 1); |
| |
| // merge 2 more times, counters should get aggregated |
| merged_profile.merge(from_proto.get()); |
| merged_profile.merge(from_proto.get()); |
| EXPECT_EQ(counter_merged->value(), 3); |
| |
| // update |
| RuntimeProfile updated_profile("updated"); |
| updated_profile.update(proto_profile); |
| RuntimeProfile::Counter* counter_updated = updated_profile.get_counter("A"); |
| EXPECT_EQ(counter_updated->value(), 1); |
| |
| // update 2 more times, counters should stay the same |
| updated_profile.update(proto_profile); |
| updated_profile.update(proto_profile); |
| EXPECT_EQ(counter_updated->value(), 1); |
| } |
| |
| void ValidateCounter(RuntimeProfile* profile, const string& name, int64_t value) { |
| RuntimeProfile::Counter* counter = profile->get_counter(name); |
| ASSERT_TRUE(counter != nullptr); |
| EXPECT_EQ(counter->value(), value); |
| } |
| |
| TEST(RuntimeProfileTest, MergeAndupdate) { |
| // Create two trees. Each tree has two children, one of which has the |
| // same name in both trees. Merging the two trees should result in 3 |
| // children, with the counters from the shared child aggregated. |
| |
| ObjectPool pool; |
| RuntimeProfile profile1("Parent1"); |
| RuntimeProfile p1_child1("Child1"); |
| RuntimeProfile p1_child2("Child2"); |
| profile1.add_child(&p1_child1, true); |
| profile1.add_child(&p1_child2, true); |
| |
| RuntimeProfile profile2("Parent2"); |
| RuntimeProfile p2_child1("Child1"); |
| RuntimeProfile p2_child3("Child3"); |
| profile2.add_child(&p2_child1, true); |
| profile2.add_child(&p2_child3, true); |
| |
| // Create parent level counters |
| RuntimeProfile::Counter* parent1_shared = profile1.add_counter("Parent Shared", TUnit::UNIT); |
| RuntimeProfile::Counter* parent2_shared = profile2.add_counter("Parent Shared", TUnit::UNIT); |
| RuntimeProfile::Counter* parent1_only = profile1.add_counter("Parent 1 Only", TUnit::UNIT); |
| RuntimeProfile::Counter* parent2_only = profile2.add_counter("Parent 2 Only", TUnit::UNIT); |
| parent1_shared->update(1); |
| parent2_shared->update(3); |
| parent1_only->update(2); |
| parent2_only->update(5); |
| |
| // Create child level counters |
| RuntimeProfile::Counter* p1_c1_shared = p1_child1.add_counter("Child1 Shared", TUnit::UNIT); |
| RuntimeProfile::Counter* p1_c1_only = |
| p1_child1.add_counter("Child1 Parent 1 Only", TUnit::UNIT); |
| RuntimeProfile::Counter* p1_c2 = p1_child2.add_counter("Child2", TUnit::UNIT); |
| RuntimeProfile::Counter* p2_c1_shared = p2_child1.add_counter("Child1 Shared", TUnit::UNIT); |
| RuntimeProfile::Counter* p2_c1_only = |
| p1_child1.add_counter("Child1 Parent 2 Only", TUnit::UNIT); |
| RuntimeProfile::Counter* p2_c3 = p2_child3.add_counter("Child3", TUnit::UNIT); |
| p1_c1_shared->update(10); |
| p1_c1_only->update(50); |
| p2_c1_shared->update(20); |
| p2_c1_only->update(100); |
| p2_c3->update(30); |
| p1_c2->update(40); |
| |
| // merge the two and validate |
| TRuntimeProfileTree tprofile1; |
| profile1.to_thrift(&tprofile1, 2); |
| std::unique_ptr<RuntimeProfile> merged_profile = RuntimeProfile::from_thrift(tprofile1); |
| merged_profile->merge(&profile2); |
| std::stringstream ss; |
| merged_profile->pretty_print(&ss); |
| std::cout << "Merged profile:\n" << ss.str() << std::endl; |
| EXPECT_EQ(4, merged_profile->num_counters()); |
| ValidateCounter(merged_profile.get(), "Parent Shared", 4); |
| ValidateCounter(merged_profile.get(), "Parent 1 Only", 2); |
| ValidateCounter(merged_profile.get(), "Parent 2 Only", 5); |
| |
| std::vector<RuntimeProfile*> children; |
| merged_profile->get_children(&children); |
| EXPECT_EQ(children.size(), 3); |
| |
| for (int i = 0; i < 3; ++i) { |
| RuntimeProfile* profile = children[i]; |
| |
| if (profile->name() == "Child1") { |
| EXPECT_EQ(4, profile->num_counters()); |
| ValidateCounter(profile, "Child1 Shared", 30); |
| ValidateCounter(profile, "Child1 Parent 1 Only", 50); |
| ValidateCounter(profile, "Child1 Parent 2 Only", 100); |
| } else if (profile->name() == "Child2") { |
| EXPECT_EQ(2, profile->num_counters()); |
| ValidateCounter(profile, "Child2", 40); |
| } else if (profile->name() == "Child3") { |
| EXPECT_EQ(2, profile->num_counters()); |
| ValidateCounter(profile, "Child3", 30); |
| } else { |
| EXPECT_TRUE(false); |
| } |
| } |
| |
| // make sure we can print |
| std::stringstream dummy; |
| merged_profile->pretty_print(&dummy); |
| |
| // update profile2 w/ profile1 and validate |
| profile2.update(tprofile1); |
| EXPECT_EQ(4, profile2.num_counters()); |
| ValidateCounter(&profile2, "Parent Shared", 1); |
| ValidateCounter(&profile2, "Parent 1 Only", 2); |
| ValidateCounter(&profile2, "Parent 2 Only", 5); |
| |
| profile2.get_children(&children); |
| EXPECT_EQ(children.size(), 3); |
| |
| for (int i = 0; i < 3; ++i) { |
| RuntimeProfile* profile = children[i]; |
| |
| if (profile->name() == "Child1") { |
| EXPECT_EQ(4, profile->num_counters()); |
| ValidateCounter(profile, "Child1 Shared", 10); |
| ValidateCounter(profile, "Child1 Parent 1 Only", 50); |
| ValidateCounter(profile, "Child1 Parent 2 Only", 100); |
| } else if (profile->name() == "Child2") { |
| EXPECT_EQ(2, profile->num_counters()); |
| ValidateCounter(profile, "Child2", 40); |
| } else if (profile->name() == "Child3") { |
| EXPECT_EQ(2, profile->num_counters()); |
| ValidateCounter(profile, "Child3", 30); |
| } else { |
| EXPECT_TRUE(false); |
| } |
| } |
| |
| // make sure we can print |
| profile2.pretty_print(&dummy); |
| } |
| |
| TEST(RuntimeProfileTest, ProtoMergeAndUpdate) { |
| ObjectPool pool; |
| RuntimeProfile profile1("Parent1"); |
| RuntimeProfile p1_child1("Child1"); |
| RuntimeProfile p1_child2("Child2"); |
| profile1.add_child(&p1_child1, true); |
| profile1.add_child(&p1_child2, true); |
| |
| RuntimeProfile profile2("Parent2"); |
| RuntimeProfile p2_child1("Child1"); |
| RuntimeProfile p2_child3("Child3"); |
| profile2.add_child(&p2_child1, true); |
| profile2.add_child(&p2_child3, true); |
| |
| // Create parent-level counters |
| RuntimeProfile::Counter* parent1_shared = profile1.add_counter("Parent Shared", TUnit::UNIT); |
| RuntimeProfile::Counter* parent2_shared = profile2.add_counter("Parent Shared", TUnit::UNIT); |
| RuntimeProfile::Counter* parent1_only = profile1.add_counter("Parent 1 Only", TUnit::UNIT); |
| RuntimeProfile::Counter* parent2_only = profile2.add_counter("Parent 2 Only", TUnit::UNIT); |
| parent1_shared->update(1); |
| parent2_shared->update(3); |
| parent1_only->update(2); |
| parent2_only->update(5); |
| |
| // Create child-level counters |
| RuntimeProfile::Counter* p1_c1_shared = p1_child1.add_counter("Child1 Shared", TUnit::UNIT); |
| RuntimeProfile::Counter* p1_c1_only = |
| p1_child1.add_counter("Child1 Parent 1 Only", TUnit::UNIT); |
| RuntimeProfile::Counter* p1_c2 = p1_child2.add_counter("Child2", TUnit::UNIT); |
| |
| RuntimeProfile::Counter* p2_c1_shared = p2_child1.add_counter("Child1 Shared", TUnit::UNIT); |
| RuntimeProfile::Counter* p2_c1_only = |
| p1_child1.add_counter("Child1 Parent 2 Only", TUnit::UNIT); |
| RuntimeProfile::Counter* p2_c3 = p2_child3.add_counter("Child3", TUnit::UNIT); |
| |
| p1_c1_shared->update(10); |
| p1_c1_only->update(50); |
| p2_c1_shared->update(20); |
| p2_c1_only->update(100); |
| p2_c3->update(30); |
| p1_c2->update(40); |
| |
| // Serialize profile1 to proto |
| PRuntimeProfileTree proto_profile1; |
| profile1.to_proto(&proto_profile1, 2); |
| |
| // Deserialize from proto and merge with profile2 |
| std::unique_ptr<RuntimeProfile> merged_profile = RuntimeProfile::from_proto(proto_profile1); |
| merged_profile->merge(&profile2); |
| |
| std::stringstream ss; |
| merged_profile->pretty_print(&ss); |
| std::cout << "Merged profile:\n" << ss.str() << std::endl; |
| |
| EXPECT_EQ(4, merged_profile->num_counters()); |
| ValidateCounter(merged_profile.get(), "Parent Shared", 4); |
| ValidateCounter(merged_profile.get(), "Parent 1 Only", 2); |
| ValidateCounter(merged_profile.get(), "Parent 2 Only", 5); |
| |
| std::vector<RuntimeProfile*> children; |
| merged_profile->get_children(&children); |
| EXPECT_EQ(children.size(), 3); |
| |
| for (RuntimeProfile* profile : children) { |
| if (profile->name() == "Child1") { |
| EXPECT_EQ(4, profile->num_counters()); |
| ValidateCounter(profile, "Child1 Shared", 30); |
| ValidateCounter(profile, "Child1 Parent 1 Only", 50); |
| ValidateCounter(profile, "Child1 Parent 2 Only", 100); |
| } else if (profile->name() == "Child2") { |
| EXPECT_EQ(2, profile->num_counters()); |
| ValidateCounter(profile, "Child2", 40); |
| } else if (profile->name() == "Child3") { |
| EXPECT_EQ(2, profile->num_counters()); |
| ValidateCounter(profile, "Child3", 30); |
| } else { |
| FAIL() << "Unexpected child profile name: " << profile->name(); |
| } |
| } |
| |
| // Test update: update profile2 with profile1's proto tree |
| profile2.update(proto_profile1); |
| EXPECT_EQ(4, profile2.num_counters()); |
| ValidateCounter(&profile2, "Parent Shared", 1); |
| ValidateCounter(&profile2, "Parent 1 Only", 2); |
| ValidateCounter(&profile2, "Parent 2 Only", 5); |
| |
| profile2.get_children(&children); |
| EXPECT_EQ(children.size(), 3); |
| |
| for (RuntimeProfile* profile : children) { |
| if (profile->name() == "Child1") { |
| EXPECT_EQ(4, profile->num_counters()); |
| ValidateCounter(profile, "Child1 Shared", 10); |
| ValidateCounter(profile, "Child1 Parent 1 Only", 50); |
| ValidateCounter(profile, "Child1 Parent 2 Only", 100); |
| } else if (profile->name() == "Child2") { |
| EXPECT_EQ(2, profile->num_counters()); |
| ValidateCounter(profile, "Child2", 40); |
| } else if (profile->name() == "Child3") { |
| EXPECT_EQ(2, profile->num_counters()); |
| ValidateCounter(profile, "Child3", 30); |
| } else { |
| FAIL() << "Unexpected child profile name: " << profile->name(); |
| } |
| } |
| |
| // Ensure pretty_print doesn't crash |
| std::stringstream dummy; |
| profile2.pretty_print(&dummy); |
| } |
| |
| TEST(RuntimeProfileTest, DerivedCounters) { |
| ObjectPool pool; |
| RuntimeProfile profile("Profile"); |
| RuntimeProfile::Counter* bytes_counter = profile.add_counter("bytes", TUnit::BYTES); |
| RuntimeProfile::Counter* ticks_counter = profile.add_counter("ticks", TUnit::TIME_NS); |
| // set to 1 sec |
| ticks_counter->set(1000L * 1000L * 1000L); |
| |
| RuntimeProfile::DerivedCounter* throughput_counter = profile.add_derived_counter( |
| "throughput", TUnit::BYTES, |
| [bytes_counter, ticks_counter] { |
| return RuntimeProfile::units_per_second(bytes_counter, ticks_counter); |
| }, |
| RuntimeProfile::ROOT_COUNTER); |
| |
| bytes_counter->set(10L); |
| EXPECT_EQ(throughput_counter->value(), 10); |
| bytes_counter->set(20L); |
| EXPECT_EQ(throughput_counter->value(), 20); |
| ticks_counter->set(ticks_counter->value() / 2); |
| EXPECT_EQ(throughput_counter->value(), 40); |
| } |
| |
| TEST(RuntimeProfileTest, InfoStringTest) { |
| ObjectPool pool; |
| RuntimeProfile profile("Profile"); |
| EXPECT_TRUE(profile.get_info_string("Key") == nullptr); |
| |
| profile.add_info_string("Key", "Value"); |
| const string* value = profile.get_info_string("Key"); |
| EXPECT_TRUE(value != nullptr); |
| EXPECT_EQ(*value, "Value"); |
| |
| // Convert it to thrift |
| TRuntimeProfileTree tprofile; |
| profile.to_thrift(&tprofile); |
| |
| // Convert it back |
| std::unique_ptr<RuntimeProfile> from_thrift = RuntimeProfile::from_thrift(tprofile); |
| value = from_thrift->get_info_string("Key"); |
| EXPECT_TRUE(value != nullptr); |
| EXPECT_EQ(*value, "Value"); |
| |
| // Test update. |
| RuntimeProfile update_dst_profile("Profile2"); |
| update_dst_profile.update(tprofile); |
| value = update_dst_profile.get_info_string("Key"); |
| EXPECT_TRUE(value != nullptr); |
| EXPECT_EQ(*value, "Value"); |
| |
| // update the original profile, convert it to thrift and update from the dst |
| // profile |
| profile.add_info_string("Key", "NewValue"); |
| profile.add_info_string("Foo", "Bar"); |
| EXPECT_EQ(*profile.get_info_string("Key"), "NewValue"); |
| EXPECT_EQ(*profile.get_info_string("Foo"), "Bar"); |
| profile.to_thrift(&tprofile); |
| |
| update_dst_profile.update(tprofile); |
| EXPECT_EQ(*update_dst_profile.get_info_string("Key"), "NewValue"); |
| EXPECT_EQ(*update_dst_profile.get_info_string("Foo"), "Bar"); |
| } |
| |
| TEST(RuntimeProfileTest, ProtoInfoStringTest) { |
| ObjectPool pool; |
| RuntimeProfile profile("Profile"); |
| |
| EXPECT_TRUE(profile.get_info_string("Key") == nullptr); |
| |
| profile.add_info_string("Key", "Value"); |
| const std::string* value = profile.get_info_string("Key"); |
| EXPECT_TRUE(value != nullptr); |
| EXPECT_EQ(*value, "Value"); |
| |
| // Convert it to proto |
| PRuntimeProfileTree pprofile; |
| profile.to_proto(&pprofile); |
| |
| // Convert it back from proto |
| std::unique_ptr<RuntimeProfile> from_proto = RuntimeProfile::from_proto(pprofile); |
| value = from_proto->get_info_string("Key"); |
| EXPECT_TRUE(value != nullptr); |
| EXPECT_EQ(*value, "Value"); |
| |
| // Test update |
| RuntimeProfile update_dst_profile("Profile2"); |
| update_dst_profile.update(pprofile); |
| value = update_dst_profile.get_info_string("Key"); |
| EXPECT_TRUE(value != nullptr); |
| EXPECT_EQ(*value, "Value"); |
| |
| // Modify original profile, convert again, and update dst |
| profile.add_info_string("Key", "NewValue"); |
| profile.add_info_string("Foo", "Bar"); |
| EXPECT_EQ(*profile.get_info_string("Key"), "NewValue"); |
| EXPECT_EQ(*profile.get_info_string("Foo"), "Bar"); |
| |
| profile.to_proto(&pprofile); |
| update_dst_profile.update(pprofile); |
| |
| EXPECT_EQ(*update_dst_profile.get_info_string("Key"), "NewValue"); |
| EXPECT_EQ(*update_dst_profile.get_info_string("Foo"), "Bar"); |
| } |
| |
| // This case could be added back when we fixed the issue. |
| // TEST(RuntimeProfileTest, AddSameCounter) { |
| // RuntimeProfile profile("Profile"); |
| // RuntimeProfile::Counter* counter = profile.add_counter("counter", TUnit::UNIT); |
| // counter->set(10L); |
| // EXPECT_EQ(counter->value(), 10L); |
| |
| // // Adding the same counter again should fail. |
| // EXPECT_THROW(profile.add_counter("counter", TUnit::UNIT), doris::Exception); |
| // } |
| |
| TEST(RuntimeProfileTest, TestGetChild) { |
| // Create root profile |
| RuntimeProfile root("Root"); |
| |
| // Test get non-existing child |
| ASSERT_EQ(nullptr, root.get_child("NonExistingChild")); |
| |
| // Create child1 and verify get_child |
| RuntimeProfile* child1 = root.create_child("Child1"); |
| ASSERT_NE(nullptr, child1); |
| ASSERT_EQ(child1, root.get_child("Child1")); |
| |
| // Create child2 and verify get_child |
| RuntimeProfile* child2 = root.create_child("Child2"); |
| ASSERT_NE(nullptr, child2); |
| ASSERT_EQ(child2, root.get_child("Child2")); |
| |
| // Create nested child and verify get_child |
| RuntimeProfile* grandchild = child1->create_child("GrandChild"); |
| ASSERT_NE(nullptr, grandchild); |
| ASSERT_EQ(grandchild, child1->get_child("GrandChild")); |
| ASSERT_EQ(nullptr, root.get_child("GrandChild")); // Cannot get grandchild directly from root |
| |
| // Verify original children still accessible |
| ASSERT_EQ(child1, root.get_child("Child1")); |
| ASSERT_EQ(child2, root.get_child("Child2")); |
| } |
| |
| } // namespace doris |