1 #include <gtest/gtest.h>
2 
3 #include "node-inl.h"
4 
5 #include <algorithm>
6 #include <memory>
7 #include <mutex>
8 
9 using mediaprovider::fuse::dirhandle;
10 using mediaprovider::fuse::handle;
11 using mediaprovider::fuse::node;
12 using mediaprovider::fuse::NodeTracker;
13 
14 // Listed as a friend class to struct node so it can observe implementation
15 // details if required. The only implementation detail that is worth writing
16 // tests around at the moment is the reference count.
17 class NodeTest : public ::testing::Test {
18   public:
NodeTest()19     NodeTest() : tracker_(NodeTracker(&lock_)) {}
20 
GetRefCount(node * node)21     uint32_t GetRefCount(node* node) { return node->refcount_; }
22 
23     std::recursive_mutex lock_;
24     NodeTracker tracker_;
25 
26     // Forward destruction here, as NodeTest is a friend class.
destroy(node * node)27     static void destroy(node* node) { delete node; }
28 
acquire(node * node)29     static void acquire(node* node) { node->Acquire(); }
30 
31     typedef std::unique_ptr<node, decltype(&NodeTest::destroy)> unique_node_ptr;
32 
CreateNode(node * parent,const std::string & path)33     unique_node_ptr CreateNode(node* parent, const std::string& path) {
34         return unique_node_ptr(node::Create(parent, path, &lock_, &tracker_), &NodeTest::destroy);
35     }
36 };
37 
TEST_F(NodeTest,TestCreate)38 TEST_F(NodeTest, TestCreate) {
39     unique_node_ptr node = CreateNode(nullptr, "/path");
40 
41     ASSERT_EQ("/path", node->GetName());
42     ASSERT_EQ(1, GetRefCount(node.get()));
43     ASSERT_FALSE(node->HasCachedHandle());
44 }
45 
TEST_F(NodeTest,TestCreate_withParent)46 TEST_F(NodeTest, TestCreate_withParent) {
47     unique_node_ptr parent = CreateNode(nullptr, "/path");
48     ASSERT_EQ(1, GetRefCount(parent.get()));
49 
50     // Adding a child to a parent node increments its refcount.
51     unique_node_ptr child = CreateNode(parent.get(), "subdir");
52     ASSERT_EQ(2, GetRefCount(parent.get()));
53 
54     // Make sure the node has been added to the parents list of children.
55     ASSERT_EQ(child.get(), parent->LookupChildByName("subdir", false /* acquire */));
56     ASSERT_EQ(1, GetRefCount(child.get()));
57 }
58 
TEST_F(NodeTest,TestRelease)59 TEST_F(NodeTest, TestRelease) {
60     node* node = node::Create(nullptr, "/path", &lock_, &tracker_);
61     acquire(node);
62     acquire(node);
63     ASSERT_EQ(3, GetRefCount(node));
64 
65     ASSERT_FALSE(node->Release(1));
66     ASSERT_EQ(2, GetRefCount(node));
67 
68     // A Release that makes refcount go negative should be a no-op.
69     ASSERT_FALSE(node->Release(10000));
70     ASSERT_EQ(2, GetRefCount(node));
71 
72     // Finally, let the refcount go to zero.
73     ASSERT_TRUE(node->Release(2));
74 }
75 
TEST_F(NodeTest,TestRenameWithName)76 TEST_F(NodeTest, TestRenameWithName) {
77     unique_node_ptr parent = CreateNode(nullptr, "/path");
78 
79     unique_node_ptr child = CreateNode(parent.get(), "subdir");
80     ASSERT_EQ(2, GetRefCount(parent.get()));
81     ASSERT_EQ(child.get(), parent->LookupChildByName("subdir", false /* acquire */));
82 
83     child->Rename("subdir_new", parent.get());
84 
85     ASSERT_EQ(2, GetRefCount(parent.get()));
86     ASSERT_EQ(nullptr, parent->LookupChildByName("subdir", false /* acquire */));
87     ASSERT_EQ(child.get(), parent->LookupChildByName("subdir_new", false /* acquire */));
88 
89     ASSERT_EQ("/path/subdir_new", child->BuildPath());
90     ASSERT_EQ(1, GetRefCount(child.get()));
91 }
92 
TEST_F(NodeTest,TestRenameWithParent)93 TEST_F(NodeTest, TestRenameWithParent) {
94     unique_node_ptr parent1 = CreateNode(nullptr, "/path1");
95     unique_node_ptr parent2 = CreateNode(nullptr, "/path2");
96 
97     unique_node_ptr child = CreateNode(parent1.get(), "subdir");
98     ASSERT_EQ(2, GetRefCount(parent1.get()));
99     ASSERT_EQ(child.get(), parent1->LookupChildByName("subdir", false /* acquire */));
100 
101     child->Rename("subdir", parent2.get());
102     ASSERT_EQ(1, GetRefCount(parent1.get()));
103     ASSERT_EQ(nullptr, parent1->LookupChildByName("subdir", false /* acquire */));
104 
105     ASSERT_EQ(2, GetRefCount(parent2.get()));
106     ASSERT_EQ(child.get(), parent2->LookupChildByName("subdir", false /* acquire */));
107 
108     ASSERT_EQ("/path2/subdir", child->BuildPath());
109     ASSERT_EQ(1, GetRefCount(child.get()));
110 }
111 
TEST_F(NodeTest,TestRenameWithNameAndParent)112 TEST_F(NodeTest, TestRenameWithNameAndParent) {
113     unique_node_ptr parent1 = CreateNode(nullptr, "/path1");
114     unique_node_ptr parent2 = CreateNode(nullptr, "/path2");
115 
116     unique_node_ptr child = CreateNode(parent1.get(), "subdir");
117     ASSERT_EQ(2, GetRefCount(parent1.get()));
118     ASSERT_EQ(child.get(), parent1->LookupChildByName("subdir", false /* acquire */));
119 
120     child->Rename("subdir_new", parent2.get());
121     ASSERT_EQ(1, GetRefCount(parent1.get()));
122     ASSERT_EQ(nullptr, parent1->LookupChildByName("subdir", false /* acquire */));
123     ASSERT_EQ(nullptr, parent1->LookupChildByName("subdir_new", false /* acquire */));
124 
125     ASSERT_EQ(2, GetRefCount(parent2.get()));
126     ASSERT_EQ(child.get(), parent2->LookupChildByName("subdir_new", false /* acquire */));
127 
128     ASSERT_EQ("/path2/subdir_new", child->BuildPath());
129     ASSERT_EQ(1, GetRefCount(child.get()));
130 }
131 
TEST_F(NodeTest,TestBuildPath)132 TEST_F(NodeTest, TestBuildPath) {
133     unique_node_ptr parent = CreateNode(nullptr, "/path");
134     ASSERT_EQ("/path", parent->BuildPath());
135 
136     unique_node_ptr child = CreateNode(parent.get(), "subdir");
137     ASSERT_EQ("/path/subdir", child->BuildPath());
138 
139     unique_node_ptr child2 = CreateNode(parent.get(), "subdir2");
140     ASSERT_EQ("/path/subdir2", child2->BuildPath());
141 
142     unique_node_ptr subchild = CreateNode(child2.get(), "subsubdir");
143     ASSERT_EQ("/path/subdir2/subsubdir", subchild->BuildPath());
144 }
145 
TEST_F(NodeTest,TestSetDeleted)146 TEST_F(NodeTest, TestSetDeleted) {
147     unique_node_ptr parent = CreateNode(nullptr, "/path");
148     unique_node_ptr child = CreateNode(parent.get(), "subdir");
149 
150     ASSERT_EQ(child.get(), parent->LookupChildByName("subdir", false /* acquire */));
151     child->SetDeleted();
152     ASSERT_EQ(nullptr, parent->LookupChildByName("subdir", false /* acquire */));
153 }
154 
TEST_F(NodeTest,DeleteTree)155 TEST_F(NodeTest, DeleteTree) {
156     unique_node_ptr parent = CreateNode(nullptr, "/path");
157 
158     // This is the tree that we intend to delete.
159     node* child = node::Create(parent.get(), "subdir", &lock_, &tracker_);
160     node::Create(child, "s1", &lock_, &tracker_);
161     node* subchild2 = node::Create(child, "s2", &lock_, &tracker_);
162     node::Create(subchild2, "sc2", &lock_, &tracker_);
163 
164     ASSERT_EQ(child, parent->LookupChildByName("subdir", false /* acquire */));
165     node::DeleteTree(child);
166     ASSERT_EQ(nullptr, parent->LookupChildByName("subdir", false /* acquire */));
167 }
168 
TEST_F(NodeTest,LookupChildByName_empty)169 TEST_F(NodeTest, LookupChildByName_empty) {
170     unique_node_ptr parent = CreateNode(nullptr, "/path");
171     unique_node_ptr child = CreateNode(parent.get(), "subdir");
172 
173     ASSERT_EQ(child.get(), parent->LookupChildByName("subdir", false /* acquire */));
174     ASSERT_EQ(nullptr, parent->LookupChildByName("", false /* acquire */));
175 }
176 
TEST_F(NodeTest,LookupChildByName_refcounts)177 TEST_F(NodeTest, LookupChildByName_refcounts) {
178     unique_node_ptr parent = CreateNode(nullptr, "/path");
179     unique_node_ptr child = CreateNode(parent.get(), "subdir");
180 
181     ASSERT_EQ(child.get(), parent->LookupChildByName("subdir", false /* acquire */));
182     ASSERT_EQ(1, GetRefCount(child.get()));
183 
184     ASSERT_EQ(child.get(), parent->LookupChildByName("subdir", true /* acquire */));
185     ASSERT_EQ(2, GetRefCount(child.get()));
186 }
187 
TEST_F(NodeTest,LookupAbsolutePath)188 TEST_F(NodeTest, LookupAbsolutePath) {
189     unique_node_ptr parent = CreateNode(nullptr, "/path");
190     unique_node_ptr child = CreateNode(parent.get(), "subdir");
191     unique_node_ptr child2 = CreateNode(parent.get(), "subdir2");
192     unique_node_ptr subchild = CreateNode(child2.get(), "subsubdir");
193 
194     ASSERT_EQ(parent.get(), node::LookupAbsolutePath(parent.get(), "/path"));
195     ASSERT_EQ(parent.get(), node::LookupAbsolutePath(parent.get(), "/path/"));
196     ASSERT_EQ(nullptr, node::LookupAbsolutePath(parent.get(), "/path2"));
197 
198     ASSERT_EQ(child.get(), node::LookupAbsolutePath(parent.get(), "/path/subdir"));
199     ASSERT_EQ(child.get(), node::LookupAbsolutePath(parent.get(), "/path/subdir/"));
200     // TODO(narayan): Are the two cases below intentional behaviour ?
201     ASSERT_EQ(child.get(), node::LookupAbsolutePath(parent.get(), "/path//subdir"));
202     ASSERT_EQ(child.get(), node::LookupAbsolutePath(parent.get(), "/path///subdir"));
203 
204     ASSERT_EQ(child2.get(), node::LookupAbsolutePath(parent.get(), "/path/subdir2"));
205     ASSERT_EQ(child2.get(), node::LookupAbsolutePath(parent.get(), "/path/subdir2/"));
206 
207     ASSERT_EQ(nullptr, node::LookupAbsolutePath(parent.get(), "/path/subdir3/"));
208 
209     ASSERT_EQ(subchild.get(), node::LookupAbsolutePath(parent.get(), "/path/subdir2/subsubdir"));
210     ASSERT_EQ(nullptr, node::LookupAbsolutePath(parent.get(), "/path/subdir/subsubdir"));
211 }
212 
TEST_F(NodeTest,AddDestroyHandle)213 TEST_F(NodeTest, AddDestroyHandle) {
214     unique_node_ptr node = CreateNode(nullptr, "/path");
215 
216     handle* h = new handle(-1, new mediaprovider::fuse::RedactionInfo, true /* cached */);
217     node->AddHandle(h);
218     ASSERT_TRUE(node->HasCachedHandle());
219 
220     node->DestroyHandle(h);
221     ASSERT_FALSE(node->HasCachedHandle());
222 
223     // Should all crash the process as the handle is no longer associated with
224     // the node in question.
225     EXPECT_DEATH(node->DestroyHandle(h), "");
226     EXPECT_DEATH(node->DestroyHandle(nullptr), "");
227     std::unique_ptr<handle> h2(
228             new handle(-1, new mediaprovider::fuse::RedactionInfo, true /* cached */));
229     EXPECT_DEATH(node->DestroyHandle(h2.get()), "");
230 }
231 
TEST_F(NodeTest,CaseInsensitive)232 TEST_F(NodeTest, CaseInsensitive) {
233     unique_node_ptr parent = CreateNode(nullptr, "/path");
234     unique_node_ptr mixed_child = CreateNode(parent.get(), "cHiLd");
235 
236     node* upper_child = parent->LookupChildByName("CHILD", false /* acquire */);
237     node* lower_child = parent->LookupChildByName("child", false /* acquire */);
238 
239     ASSERT_EQ(mixed_child.get(), lower_child);
240     ASSERT_EQ(mixed_child.get(), upper_child);
241 }
242