1 #include <gtest/gtest.h>
2 
3 #include "node-inl.h"
4 
5 #include <algorithm>
6 #include <limits>
7 #include <memory>
8 #include <mutex>
9 
10 using mediaprovider::fuse::dirhandle;
11 using mediaprovider::fuse::handle;
12 using mediaprovider::fuse::node;
13 using mediaprovider::fuse::NodeTracker;
14 
15 // Listed as a friend class to struct node so it can observe implementation
16 // details if required. The only implementation detail that is worth writing
17 // tests around at the moment is the reference count.
18 class NodeTest : public ::testing::Test {
19   public:
NodeTest()20     NodeTest() : tracker_(NodeTracker(&lock_)) {}
21 
GetRefCount(node * node)22     uint32_t GetRefCount(node* node) { return node->refcount_; }
23 
24     std::recursive_mutex lock_;
25     NodeTracker tracker_;
26 
27     // Forward destruction here, as NodeTest is a friend class.
destroy(node * node)28     static void destroy(node* node) { delete node; }
29 
acquire(node * node)30     static void acquire(node* node) { node->Acquire(); }
31 
32     typedef std::unique_ptr<node, decltype(&NodeTest::destroy)> unique_node_ptr;
33 
CreateNode(node * parent,const std::string & path,const int transforms=0)34     unique_node_ptr CreateNode(node* parent, const std::string& path, const int transforms = 0) {
35         return unique_node_ptr(
36                 node::Create(parent, path, "", true, true, transforms, 0, &lock_, 0, &tracker_),
37                 &NodeTest::destroy);
38     }
39 
40     static class node* ForChild(class node* node, const std::string& name,
41                                 const std::function<bool(class node*)>& callback) {
42         return node->ForChild(name, callback);
43     }
44 
45     // Expose NodeCompare for testing.
46     node::NodeCompare cmp;
47 };
48 
TEST_F(NodeTest,TestCreate)49 TEST_F(NodeTest, TestCreate) {
50     unique_node_ptr node = CreateNode(nullptr, "/path");
51 
52     ASSERT_EQ("/path", node->GetName());
53     ASSERT_EQ(1, GetRefCount(node.get()));
54     ASSERT_FALSE(node->HasCachedHandle());
55 }
56 
TEST_F(NodeTest,TestCreate_withParent)57 TEST_F(NodeTest, TestCreate_withParent) {
58     unique_node_ptr parent = CreateNode(nullptr, "/path");
59     ASSERT_EQ(1, GetRefCount(parent.get()));
60 
61     // Adding a child to a parent node increments its refcount.
62     unique_node_ptr child = CreateNode(parent.get(), "subdir");
63     ASSERT_EQ(2, GetRefCount(parent.get()));
64 
65     // Make sure the node has been added to the parents list of children.
66     ASSERT_EQ(child.get(), parent->LookupChildByName("subdir", false /* acquire */));
67     ASSERT_EQ(1, GetRefCount(child.get()));
68 }
69 
TEST_F(NodeTest,TestRelease)70 TEST_F(NodeTest, TestRelease) {
71     node* node = node::Create(nullptr, "/path", "", false, true, 0, 0, &lock_, 0, &tracker_);
72     acquire(node);
73     acquire(node);
74     ASSERT_EQ(3, GetRefCount(node));
75 
76     ASSERT_FALSE(node->Release(1));
77     ASSERT_EQ(2, GetRefCount(node));
78 
79     // A Release that makes refcount go negative should be a no-op.
80     ASSERT_FALSE(node->Release(10000));
81     ASSERT_EQ(2, GetRefCount(node));
82 
83     // Finally, let the refcount go to zero.
84     ASSERT_TRUE(node->Release(2));
85 }
86 
TEST_F(NodeTest,TestRenameName)87 TEST_F(NodeTest, TestRenameName) {
88     unique_node_ptr parent = CreateNode(nullptr, "/path");
89 
90     unique_node_ptr child = CreateNode(parent.get(), "subdir");
91     ASSERT_EQ(2, GetRefCount(parent.get()));
92     ASSERT_EQ(child.get(), parent->LookupChildByName("subdir", false /* acquire */));
93 
94     child->Rename("subdir_new", parent.get());
95 
96     ASSERT_EQ(2, GetRefCount(parent.get()));
97     ASSERT_EQ(nullptr, parent->LookupChildByName("subdir", false /* acquire */));
98     ASSERT_EQ(child.get(), parent->LookupChildByName("subdir_new", false /* acquire */));
99 
100     ASSERT_EQ("/path/subdir_new", child->BuildPath());
101     ASSERT_EQ(1, GetRefCount(child.get()));
102 }
103 
TEST_F(NodeTest,TestRenameParent)104 TEST_F(NodeTest, TestRenameParent) {
105     unique_node_ptr parent1 = CreateNode(nullptr, "/path1");
106     unique_node_ptr parent2 = CreateNode(nullptr, "/path2");
107 
108     unique_node_ptr child = CreateNode(parent1.get(), "subdir");
109     ASSERT_EQ(2, GetRefCount(parent1.get()));
110     ASSERT_EQ(child.get(), parent1->LookupChildByName("subdir", false /* acquire */));
111 
112     child->Rename("subdir", parent2.get());
113     ASSERT_EQ(1, GetRefCount(parent1.get()));
114     ASSERT_EQ(nullptr, parent1->LookupChildByName("subdir", false /* acquire */));
115 
116     ASSERT_EQ(2, GetRefCount(parent2.get()));
117     ASSERT_EQ(child.get(), parent2->LookupChildByName("subdir", false /* acquire */));
118 
119     ASSERT_EQ("/path2/subdir", child->BuildPath());
120     ASSERT_EQ(1, GetRefCount(child.get()));
121 }
122 
TEST_F(NodeTest,TestRenameNameAndParent)123 TEST_F(NodeTest, TestRenameNameAndParent) {
124     unique_node_ptr parent1 = CreateNode(nullptr, "/path1");
125     unique_node_ptr parent2 = CreateNode(nullptr, "/path2");
126 
127     unique_node_ptr child = CreateNode(parent1.get(), "subdir");
128     ASSERT_EQ(2, GetRefCount(parent1.get()));
129     ASSERT_EQ(child.get(), parent1->LookupChildByName("subdir", false /* acquire */));
130 
131     child->Rename("subdir_new", parent2.get());
132     ASSERT_EQ(1, GetRefCount(parent1.get()));
133     ASSERT_EQ(nullptr, parent1->LookupChildByName("subdir", false /* acquire */));
134     ASSERT_EQ(nullptr, parent1->LookupChildByName("subdir_new", false /* acquire */));
135 
136     ASSERT_EQ(2, GetRefCount(parent2.get()));
137     ASSERT_EQ(child.get(), parent2->LookupChildByName("subdir_new", false /* acquire */));
138 
139     ASSERT_EQ("/path2/subdir_new", child->BuildPath());
140     ASSERT_EQ(1, GetRefCount(child.get()));
141 }
142 
TEST_F(NodeTest,TestRenameNameForChild)143 TEST_F(NodeTest, TestRenameNameForChild) {
144     unique_node_ptr parent = CreateNode(nullptr, "/path");
145 
146     unique_node_ptr child0 = CreateNode(parent.get(), "subdir", 0 /* transforms */);
147     unique_node_ptr child1 = CreateNode(parent.get(), "subdir", 1 /* transforms */);
148     ASSERT_EQ(3, GetRefCount(parent.get()));
149     ASSERT_EQ(child0.get(),
150               parent->LookupChildByName("subdir", false /* acquire */, 0 /* transforms */));
151     ASSERT_EQ(child1.get(),
152               parent->LookupChildByName("subdir", false /* acquire */, 1 /* transforms */));
153 
154     parent->RenameChild("subdir", "subdir_new", parent.get());
155 
156     ASSERT_EQ(3, GetRefCount(parent.get()));
157     ASSERT_EQ(nullptr,
158               parent->LookupChildByName("subdir", false /* acquire */, 0 /* transforms */));
159     ASSERT_EQ(nullptr,
160               parent->LookupChildByName("subdir", false /* acquire */, 1 /* transforms */));
161     ASSERT_EQ(child0.get(),
162               parent->LookupChildByName("subdir_new", false /* acquire */, 0 /* transforms */));
163     ASSERT_EQ(child1.get(),
164               parent->LookupChildByName("subdir_new", false /* acquire */, 1 /* transforms */));
165 
166     ASSERT_EQ("/path/subdir_new", child0->BuildPath());
167     ASSERT_EQ("/path/subdir_new", child1->BuildPath());
168     ASSERT_EQ(1, GetRefCount(child0.get()));
169     ASSERT_EQ(1, GetRefCount(child1.get()));
170 }
171 
TEST_F(NodeTest,TestRenameParentForChild)172 TEST_F(NodeTest, TestRenameParentForChild) {
173     unique_node_ptr parent1 = CreateNode(nullptr, "/path1");
174     unique_node_ptr parent2 = CreateNode(nullptr, "/path2");
175 
176     unique_node_ptr child0 = CreateNode(parent1.get(), "subdir", 0 /* transforms */);
177     unique_node_ptr child1 = CreateNode(parent1.get(), "subdir", 1 /* transforms */);
178     ASSERT_EQ(3, GetRefCount(parent1.get()));
179     ASSERT_EQ(child0.get(),
180               parent1->LookupChildByName("subdir", false /* acquire */, 0 /* transforms */));
181     ASSERT_EQ(child1.get(),
182               parent1->LookupChildByName("subdir", false /* acquire */, 1 /* transforms */));
183 
184     parent1->RenameChild("subdir", "subdir", parent2.get());
185     ASSERT_EQ(1, GetRefCount(parent1.get()));
186     ASSERT_EQ(nullptr,
187               parent1->LookupChildByName("subdir", false /* acquire */, 0 /* transforms */));
188     ASSERT_EQ(nullptr,
189               parent1->LookupChildByName("subdir", false /* acquire */, 1 /* transforms */));
190 
191     ASSERT_EQ(3, GetRefCount(parent2.get()));
192     ASSERT_EQ(child0.get(),
193               parent2->LookupChildByName("subdir", false /* acquire */, 0 /* transforms */));
194     ASSERT_EQ(child1.get(),
195               parent2->LookupChildByName("subdir", false /* acquire */, 1 /* transforms */));
196 
197     ASSERT_EQ("/path2/subdir", child0->BuildPath());
198     ASSERT_EQ("/path2/subdir", child1->BuildPath());
199     ASSERT_EQ(1, GetRefCount(child0.get()));
200     ASSERT_EQ(1, GetRefCount(child1.get()));
201 }
202 
TEST_F(NodeTest,TestRenameNameAndParentForChild)203 TEST_F(NodeTest, TestRenameNameAndParentForChild) {
204     unique_node_ptr parent1 = CreateNode(nullptr, "/path1");
205     unique_node_ptr parent2 = CreateNode(nullptr, "/path2");
206 
207     unique_node_ptr child0 = CreateNode(parent1.get(), "subdir", 0 /* transforms */);
208     unique_node_ptr child1 = CreateNode(parent1.get(), "subdir", 1 /* transforms */);
209     ASSERT_EQ(3, GetRefCount(parent1.get()));
210     ASSERT_EQ(child0.get(),
211               parent1->LookupChildByName("subdir", false /* acquire */, 0 /* transforms */));
212     ASSERT_EQ(child1.get(),
213               parent1->LookupChildByName("subdir", false /* acquire */, 1 /* transforms */));
214 
215     parent1->RenameChild("subdir", "subdir_new", parent2.get());
216     ASSERT_EQ(1, GetRefCount(parent1.get()));
217     ASSERT_EQ(nullptr,
218               parent1->LookupChildByName("subdir", false /* acquire */, 0 /* transforms */));
219     ASSERT_EQ(nullptr,
220               parent1->LookupChildByName("subdir_new", false /* acquire */, 0 /* transforms */));
221     ASSERT_EQ(nullptr,
222               parent1->LookupChildByName("subdir", false /* acquire */, 1 /* transforms */));
223     ASSERT_EQ(nullptr,
224               parent1->LookupChildByName("subdir_new", false /* acquire */, 1 /* transforms */));
225 
226     ASSERT_EQ(3, GetRefCount(parent2.get()));
227     ASSERT_EQ(nullptr,
228               parent1->LookupChildByName("subdir_new", false /* acquire */, 0 /* transforms */));
229     ASSERT_EQ(nullptr,
230               parent1->LookupChildByName("subdir_new", false /* acquire */, 1 /* transforms */));
231 
232     ASSERT_EQ("/path2/subdir_new", child0->BuildPath());
233     ASSERT_EQ("/path2/subdir_new", child1->BuildPath());
234     ASSERT_EQ(1, GetRefCount(child0.get()));
235     ASSERT_EQ(1, GetRefCount(child1.get()));
236 }
237 
TEST_F(NodeTest,TestBuildPath)238 TEST_F(NodeTest, TestBuildPath) {
239     unique_node_ptr parent = CreateNode(nullptr, "/path");
240     ASSERT_EQ("/path", parent->BuildPath());
241 
242     unique_node_ptr child = CreateNode(parent.get(), "subdir");
243     ASSERT_EQ("/path/subdir", child->BuildPath());
244 
245     unique_node_ptr child2 = CreateNode(parent.get(), "subdir2");
246     ASSERT_EQ("/path/subdir2", child2->BuildPath());
247 
248     unique_node_ptr subchild = CreateNode(child2.get(), "subsubdir");
249     ASSERT_EQ("/path/subdir2/subsubdir", subchild->BuildPath());
250 }
251 
TEST_F(NodeTest,TestSetDeleted)252 TEST_F(NodeTest, TestSetDeleted) {
253     unique_node_ptr parent = CreateNode(nullptr, "/path");
254     unique_node_ptr child = CreateNode(parent.get(), "subdir");
255 
256     ASSERT_EQ(child.get(), parent->LookupChildByName("subdir", false /* acquire */));
257     child->SetDeleted();
258     ASSERT_EQ(nullptr, parent->LookupChildByName("subdir", false /* acquire */));
259 }
260 
TEST_F(NodeTest,TestSetDeletedForChild)261 TEST_F(NodeTest, TestSetDeletedForChild) {
262     unique_node_ptr parent = CreateNode(nullptr, "/path");
263     unique_node_ptr child0 = CreateNode(parent.get(), "subdir", 0 /* transforms */);
264     unique_node_ptr child1 = CreateNode(parent.get(), "subdir", 1 /* transforms */);
265 
266     ASSERT_EQ(child0.get(),
267               parent->LookupChildByName("subdir", false /* acquire */, 0 /* transforms */));
268     ASSERT_EQ(child1.get(),
269               parent->LookupChildByName("subdir", false /* acquire */, 1 /* transforms */));
270     parent->SetDeletedForChild("subdir");
271     ASSERT_EQ(nullptr,
272               parent->LookupChildByName("subdir", false /* acquire */, 0 /* transforms */));
273     ASSERT_EQ(nullptr,
274               parent->LookupChildByName("subdir", false /* acquire */, 1 /* transforms */));
275 }
276 
TEST_F(NodeTest,DeleteTree)277 TEST_F(NodeTest, DeleteTree) {
278     unique_node_ptr parent = CreateNode(nullptr, "/path");
279 
280     // This is the tree that we intend to delete.
281     node* child = node::Create(parent.get(), "subdir", "", false, true, 0, 0, &lock_, 0, &tracker_);
282     node::Create(child, "s1", "", false, true, 0, 0, &lock_, 0, &tracker_);
283     node* subchild2 = node::Create(child, "s2", "", false, true, 0, 0, &lock_, 0, &tracker_);
284     node::Create(subchild2, "sc2", "", false, true, 0, 0, &lock_, 0, &tracker_);
285 
286     ASSERT_EQ(child, parent->LookupChildByName("subdir", false /* acquire */));
287     node::DeleteTree(child);
288     ASSERT_EQ(nullptr, parent->LookupChildByName("subdir", false /* acquire */));
289 }
290 
TEST_F(NodeTest,LookupChildByName_empty)291 TEST_F(NodeTest, LookupChildByName_empty) {
292     unique_node_ptr parent = CreateNode(nullptr, "/path");
293     unique_node_ptr child = CreateNode(parent.get(), "subdir");
294 
295     ASSERT_EQ(child.get(), parent->LookupChildByName("subdir", false /* acquire */));
296     ASSERT_EQ(nullptr, parent->LookupChildByName("", false /* acquire */));
297 }
298 
TEST_F(NodeTest,LookupChildByName_transforms)299 TEST_F(NodeTest, LookupChildByName_transforms) {
300     unique_node_ptr parent = CreateNode(nullptr, "/path");
301     unique_node_ptr child0 = CreateNode(parent.get(), "subdir", 0 /* transforms */);
302     unique_node_ptr child1 = CreateNode(parent.get(), "subdir", 1 /* transforms */);
303 
304     ASSERT_EQ(child0.get(), parent->LookupChildByName("subdir", false /* acquire */));
305     ASSERT_EQ(child0.get(),
306               parent->LookupChildByName("subdir", false /* acquire */, 0 /* transforms */));
307     ASSERT_EQ(child1.get(),
308               parent->LookupChildByName("subdir", false /* acquire */, 1 /* transforms */));
309     ASSERT_EQ(nullptr,
310               parent->LookupChildByName("subdir", false /* acquire */, 2 /* transforms */));
311 }
312 
TEST_F(NodeTest,LookupChildByName_refcounts)313 TEST_F(NodeTest, LookupChildByName_refcounts) {
314     unique_node_ptr parent = CreateNode(nullptr, "/path");
315     unique_node_ptr child = CreateNode(parent.get(), "subdir");
316 
317     ASSERT_EQ(child.get(), parent->LookupChildByName("subdir", false /* acquire */));
318     ASSERT_EQ(1, GetRefCount(child.get()));
319 
320     ASSERT_EQ(child.get(), parent->LookupChildByName("subdir", true /* acquire */));
321     ASSERT_EQ(2, GetRefCount(child.get()));
322 }
323 
TEST_F(NodeTest,LookupAbsolutePath)324 TEST_F(NodeTest, LookupAbsolutePath) {
325     unique_node_ptr parent = CreateNode(nullptr, "/path");
326     unique_node_ptr child = CreateNode(parent.get(), "subdir");
327     unique_node_ptr child2 = CreateNode(parent.get(), "subdir2");
328     unique_node_ptr subchild = CreateNode(child2.get(), "subsubdir");
329 
330     ASSERT_EQ(parent.get(), node::LookupAbsolutePath(parent.get(), "/path"));
331     ASSERT_EQ(parent.get(), node::LookupAbsolutePath(parent.get(), "/path/"));
332     ASSERT_EQ(nullptr, node::LookupAbsolutePath(parent.get(), "/path2"));
333 
334     ASSERT_EQ(child.get(), node::LookupAbsolutePath(parent.get(), "/path/subdir"));
335     ASSERT_EQ(child.get(), node::LookupAbsolutePath(parent.get(), "/path/subdir/"));
336     // TODO(narayan): Are the two cases below intentional behaviour ?
337     ASSERT_EQ(child.get(), node::LookupAbsolutePath(parent.get(), "/path//subdir"));
338     ASSERT_EQ(child.get(), node::LookupAbsolutePath(parent.get(), "/path///subdir"));
339 
340     ASSERT_EQ(child2.get(), node::LookupAbsolutePath(parent.get(), "/path/subdir2"));
341     ASSERT_EQ(child2.get(), node::LookupAbsolutePath(parent.get(), "/path/subdir2/"));
342 
343     ASSERT_EQ(nullptr, node::LookupAbsolutePath(parent.get(), "/path/subdir3/"));
344 
345     ASSERT_EQ(subchild.get(), node::LookupAbsolutePath(parent.get(), "/path/subdir2/subsubdir"));
346     ASSERT_EQ(nullptr, node::LookupAbsolutePath(parent.get(), "/path/subdir/subsubdir"));
347 }
348 
TEST_F(NodeTest,AddDestroyHandle)349 TEST_F(NodeTest, AddDestroyHandle) {
350     unique_node_ptr node = CreateNode(nullptr, "/path");
351 
352     handle* h = new handle(-1, new mediaprovider::fuse::RedactionInfo, true /* cached */,
353                            false /* passthrough */, 0 /* uid */, 0 /* transforms_uid */);
354     node->AddHandle(h);
355     ASSERT_TRUE(node->HasCachedHandle());
356 
357     node->DestroyHandle(h);
358     ASSERT_FALSE(node->HasCachedHandle());
359 
360     // Should all crash the process as the handle is no longer associated with
361     // the node in question.
362     EXPECT_DEATH(node->DestroyHandle(h), "");
363     EXPECT_DEATH(node->DestroyHandle(nullptr), "");
364     std::unique_ptr<handle> h2(new handle(-1, new mediaprovider::fuse::RedactionInfo,
365                                           true /* cached */, false /* passthrough */, 0 /* uid */,
366                                           0 /* transforms_uid */));
367     EXPECT_DEATH(node->DestroyHandle(h2.get()), "");
368 }
369 
TEST_F(NodeTest,CaseInsensitive)370 TEST_F(NodeTest, CaseInsensitive) {
371     unique_node_ptr parent = CreateNode(nullptr, "/path");
372     unique_node_ptr mixed_child = CreateNode(parent.get(), "cHiLd");
373 
374     node* upper_child = parent->LookupChildByName("CHILD", false /* acquire */);
375     node* lower_child = parent->LookupChildByName("child", false /* acquire */);
376 
377     ASSERT_EQ(mixed_child.get(), lower_child);
378     ASSERT_EQ(mixed_child.get(), upper_child);
379 }
380 
TEST_F(NodeTest,RenameSameNameSameParent)381 TEST_F(NodeTest, RenameSameNameSameParent) {
382     unique_node_ptr parent = CreateNode(nullptr, "/path1");
383     unique_node_ptr child = CreateNode(parent.get(), "subdir");
384 
385     ASSERT_EQ(child.get(), parent->LookupChildByName("SuBdIr", false /* acquire */));
386     ASSERT_EQ(2, GetRefCount(parent.get()));
387 
388     child->Rename("subdir", parent.get());
389 
390     ASSERT_EQ(child.get(), parent->LookupChildByName("SuBdIr", false /* acquire */));
391     ASSERT_EQ(2, GetRefCount(parent.get()));
392 }
393 
TEST_F(NodeTest,RenameRoot)394 TEST_F(NodeTest, RenameRoot) {
395     unique_node_ptr root = CreateNode(nullptr, "/root");
396     ASSERT_EQ(1, GetRefCount(root.get()));
397 
398     root->Rename("/i-am-root!", nullptr);
399 
400     ASSERT_EQ("/i-am-root!", root->GetName());
401     ASSERT_EQ(1, GetRefCount(root.get()));
402 }
403 
TEST_F(NodeTest,NodeCompareDefinesLinearOrder)404 TEST_F(NodeTest, NodeCompareDefinesLinearOrder) {
405     unique_node_ptr node_a = CreateNode(nullptr, "a");
406     unique_node_ptr node_b = CreateNode(nullptr, "B");
407     unique_node_ptr node_c = CreateNode(nullptr, "c");
408 
409     ASSERT_FALSE(cmp.operator()(node_a.get(), node_a.get()));
410     ASSERT_FALSE(cmp.operator()(node_b.get(), node_b.get()));
411     ASSERT_FALSE(cmp.operator()(node_c.get(), node_c.get()));
412 
413     auto check_fn = [&](const node* lhs_node, const node* rhs_node) {
414         ASSERT_TRUE(cmp.operator()(lhs_node, rhs_node));
415         ASSERT_FALSE(cmp.operator()(rhs_node, lhs_node));
416     };
417 
418     check_fn(node_a.get(), node_b.get());
419     check_fn(node_b.get(), node_c.get());
420     check_fn(node_a.get(), node_c.get());
421 
422     // ("A", 0) < node_a < ("a", max_uintptr_t) < node_b
423     ASSERT_TRUE(cmp.operator()(std::make_pair("A", 0), node_a.get()));
424     ASSERT_TRUE(cmp.operator()(node_a.get(),
425                                std::make_pair("A", std::numeric_limits<uintptr_t>::max())));
426     ASSERT_TRUE(cmp.operator()(std::make_pair("A", std::numeric_limits<uintptr_t>::max()),
427                                node_b.get()));
428 }
429 
TEST_F(NodeTest,LookupChildByName_ChildrenWithSameName)430 TEST_F(NodeTest, LookupChildByName_ChildrenWithSameName) {
431     unique_node_ptr parent = CreateNode(nullptr, "/path");
432     unique_node_ptr foo1 = CreateNode(parent.get(), "FoO");
433     unique_node_ptr foo2 = CreateNode(parent.get(), "fOo");
434     unique_node_ptr bar1 = CreateNode(parent.get(), "BAR");
435     unique_node_ptr bar2 = CreateNode(parent.get(), "bar");
436     unique_node_ptr baz1 = CreateNode(parent.get(), "baZ");
437     unique_node_ptr baz2 = CreateNode(parent.get(), "Baz");
438 
439     auto test_fn = [&](const std::string& name, node* first, node* second) {
440         auto node1 = parent->LookupChildByName(name, false /* acquire */);
441         ASSERT_EQ(std::min(first, second), node1);
442         node1->SetDeleted();
443 
444         auto node2 = parent->LookupChildByName(name, false /* acquire */);
445         ASSERT_EQ(std::max(first, second), node2);
446         node2->SetDeleted();
447 
448         ASSERT_EQ(nullptr, parent->LookupChildByName(name, false /* acquire */));
449     };
450 
451     test_fn("foo", foo1.get(), foo2.get());
452     test_fn("bAr", bar1.get(), bar2.get());
453     test_fn("BaZ", baz1.get(), baz2.get());
454 }
455 
TEST_F(NodeTest,ForChild)456 TEST_F(NodeTest, ForChild) {
457     unique_node_ptr parent = CreateNode(nullptr, "/path");
458     unique_node_ptr foo1 = CreateNode(parent.get(), "FoO");
459     unique_node_ptr foo2 = CreateNode(parent.get(), "fOo");
460     unique_node_ptr foo3 = CreateNode(parent.get(), "foo");
461     foo3->SetDeleted();
462 
463     std::vector<node*> match_all;
464     auto test_fn_match_all = [&](node* child) {
465         match_all.push_back(child);
466         return false;
467     };
468 
469     std::vector<node*> match_first;
470     auto test_fn_match_first = [&](node* child) {
471         match_first.push_back(child);
472         return true;
473     };
474 
475     std::vector<node*> match_none;
476     auto test_fn_match_none = [&](node* child) {
477         match_none.push_back(child);
478         return false;
479     };
480 
481     node* node_all = ForChild(parent.get(), "foo", test_fn_match_all);
482     ASSERT_EQ(nullptr, node_all);
483     ASSERT_EQ(2, match_all.size());
484     ASSERT_EQ(std::min(foo1.get(), foo2.get()), match_all[0]);
485     ASSERT_EQ(std::max(foo1.get(), foo2.get()), match_all[1]);
486 
487     node* node_first = ForChild(parent.get(), "foo", test_fn_match_first);
488     ASSERT_EQ(std::min(foo1.get(), foo2.get()), node_first);
489     ASSERT_EQ(1, match_first.size());
490     ASSERT_EQ(std::min(foo1.get(), foo2.get()), match_first[0]);
491 
492     node* node_none = ForChild(parent.get(), "bar", test_fn_match_none);
493     ASSERT_EQ(nullptr, node_none);
494     ASSERT_TRUE(match_none.empty());
495 }
496