fuchsia_inspect_contrib/nodes/
list.rs1use fuchsia_inspect::{Error, Node};
6use std::collections::VecDeque;
7
8#[derive(Debug)]
16pub struct BoundedListNode {
17 node: Node,
18 index: usize,
19 capacity: usize,
20 items: VecDeque<Node>,
21}
22
23impl BoundedListNode {
24 pub fn new(node: Node, capacity: usize) -> Self {
26 Self {
27 node,
28 index: 0,
29 capacity: std::cmp::max(capacity, 1),
30 items: VecDeque::with_capacity(capacity),
31 }
32 }
33
34 pub fn len(&self) -> usize {
36 self.items.len()
37 }
38
39 pub fn is_empty(&self) -> bool {
41 self.items.len() == 0
42 }
43
44 pub fn capacity(&self) -> usize {
46 self.capacity
47 }
48
49 pub fn add_entry<F>(&mut self, initialize: F) -> &Node
58 where
59 F: FnOnce(&Node),
60 {
61 if self.items.len() >= self.capacity {
62 self.items.pop_front();
63 }
64
65 let entry_node = self.node.atomic_update(|node| {
66 let child = node.create_child(self.index.to_string());
67 initialize(&child);
68 child
69 });
70 self.items.push_back(entry_node);
71
72 self.index += 1;
73 self.items.back().unwrap()
74 }
75
76 pub fn adopt_entry(&mut self, entry: Node) -> Result<&Node, Error> {
83 self.node.atomic_update(|node| {
84 node.adopt(&entry)?;
85 entry.rename(self.index.to_string())
86 })?;
87
88 if self.items.len() >= self.capacity {
89 self.items.pop_front();
90 }
91
92 self.items.push_back(entry);
93 self.index += 1;
94 Ok(self.items.back().unwrap())
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use assert_matches::assert_matches;
102 use diagnostics_assertions::assert_data_tree;
103 use fuchsia_inspect::Inspector;
104 use fuchsia_inspect::reader::{self, ReaderError};
105 use std::sync::mpsc;
106
107 #[fuchsia::test]
108 async fn test_bounded_list_node_basic() {
109 let inspector = Inspector::default();
110 let list_node = inspector.root().create_child("list_node");
111 let mut list_node = BoundedListNode::new(list_node, 3);
112 assert_eq!(list_node.capacity(), 3);
113 assert_eq!(list_node.len(), 0);
114 let _ = list_node.add_entry(|_| {});
115 assert_eq!(list_node.len(), 1);
116 assert_data_tree!(inspector, root: { list_node: { "0": {} } });
117 let _ = list_node.add_entry(|_| {});
118 assert_eq!(list_node.len(), 2);
119 assert_data_tree!(inspector, root: { list_node: { "0": {}, "1": {} } });
120 }
121
122 #[fuchsia::test]
123 async fn test_bounded_list_node_eviction() {
124 let inspector = Inspector::default();
125 let list_node = inspector.root().create_child("list_node");
126 let mut list_node = BoundedListNode::new(list_node, 3);
127 let _ = list_node.add_entry(|_| {});
128 let _ = list_node.add_entry(|_| {});
129 let _ = list_node.add_entry(|_| {});
130
131 assert_data_tree!(inspector, root: { list_node: { "0": {}, "1": {}, "2": {} } });
132 assert_eq!(list_node.len(), 3);
133
134 let _ = list_node.add_entry(|_| {});
135 assert_data_tree!(inspector, root: { list_node: { "1": {}, "2": {}, "3": {} } });
136 assert_eq!(list_node.len(), 3);
137
138 let _ = list_node.add_entry(|_| {});
139 assert_data_tree!(inspector, root: { list_node: { "2": {}, "3": {}, "4": {} } });
140 assert_eq!(list_node.len(), 3);
141 }
142
143 #[fuchsia::test]
144 async fn test_bounded_list_node_specified_zero_capacity() {
145 let inspector = Inspector::default();
146 let list_node = inspector.root().create_child("list_node");
147 let mut list_node = BoundedListNode::new(list_node, 0);
148 let _ = list_node.add_entry(|_| {});
149 assert_data_tree!(inspector, root: { list_node: { "0": {} } });
150 let _ = list_node.add_entry(|_| {});
151 assert_data_tree!(inspector, root: { list_node: { "1": {} } });
152 }
153
154 #[fuchsia::test]
155 async fn test_bounded_list_node_holds_its_values() {
156 let inspector = Inspector::default();
157 let list_node = inspector.root().create_child("list_node");
158 let mut list_node = BoundedListNode::new(list_node, 3);
159
160 {
161 let node_writer = list_node.add_entry(|_| {});
162 node_writer.record_string("str_key", "str_value");
163 node_writer.record_child("child", |child| child.record_int("int_key", 2));
164 } assert_data_tree!(inspector, root: {
168 list_node: {
169 "0": {
170 str_key: "str_value",
171 child: {
172 int_key: 2i64,
173 }
174 }
175 }
176 });
177 }
178
179 #[fuchsia::test]
180 async fn add_entry_is_atomic() {
181 let inspector = Inspector::default();
182 let list_node = inspector.root().create_child("list_node");
183 let mut list_node = BoundedListNode::new(list_node, 3);
184
185 let (sender, receiver) = mpsc::channel();
186 let (sender2, receiver2) = mpsc::channel();
187
188 let t = std::thread::spawn(move || {
189 list_node.add_entry(|node| {
190 node.record_string("key1", "value1");
191 sender.send(()).unwrap();
192 receiver2.recv().unwrap();
193 node.record_string("key2", "value2");
194 });
195 list_node
196 });
197
198 receiver.recv().unwrap();
200
201 assert_matches!(reader::read(&inspector).await, Err(ReaderError::InconsistentSnapshot));
203
204 sender2.send(()).unwrap();
206
207 let _list_node = t.join().unwrap();
209
210 assert_data_tree!(inspector, root: {
212 list_node: {
213 "0": {
214 key1: "value1",
215 key2: "value2",
216 }
217 }
218 });
219 }
220
221 #[fuchsia::test]
222 async fn test_bounded_list_node_adopt_entry() {
223 let inspector = Inspector::default();
224 let list_node = inspector.root().create_child("list_node");
225 let mut list_node = BoundedListNode::new(list_node, 3);
226
227 let first = inspector.root().create_child("first");
228 first.record_int("val", 10);
229 assert_data_tree!(inspector, root: {
230 list_node: {},
231 first: { val: 10i64 },
232 });
233
234 let adopted = list_node.adopt_entry(first).unwrap();
235 adopted.record_string("extra", "hello");
236 assert_eq!(list_node.len(), 1);
237 assert_data_tree!(inspector, root: {
238 list_node: {
239 "0": {
240 val: 10i64,
241 extra: "hello",
242 },
243 },
244 });
245
246 list_node.add_entry(|n| n.record_int("val", 20));
247 let third = inspector.root().create_child("third");
248 third.record_int("val", 30);
249 list_node.adopt_entry(third).unwrap();
250 assert_eq!(list_node.len(), 3);
251 assert_data_tree!(inspector, root: {
252 list_node: {
253 "0": {
254 val: 10i64,
255 extra: "hello",
256 },
257 "1": {
258 val: 20i64,
259 },
260 "2": {
261 val: 30i64,
262 },
263 },
264 });
265
266 let fourth = inspector.root().create_child("fourth");
268 fourth.record_int("val", 40);
269 list_node.adopt_entry(fourth).unwrap();
270 assert_eq!(list_node.len(), 3);
271 assert_data_tree!(inspector, root: {
272 list_node: {
273 "1": {
274 val: 20i64,
275 },
276 "2": {
277 val: 30i64,
278 },
279 "3": {
280 val: 40i64,
281 },
282 },
283 });
284 }
285
286 #[fuchsia::test]
287 async fn test_bounded_list_node_adopt_entry_error() {
288 let inspector = Inspector::default();
289 let parent = inspector.root().create_child("parent");
290 let list_node = parent.create_child("list_node");
291 let mut list_node = BoundedListNode::new(list_node, 1);
292
293 list_node.add_entry(|n| n.record_int("val", 1));
294 assert_eq!(list_node.len(), 1);
295
296 let other_inspector = Inspector::default();
298 let other_node = other_inspector.root().create_child("other");
299 assert_matches!(list_node.adopt_entry(other_node), Err(Error::AdoptionIntoWrongVmo));
300 assert_data_tree!(inspector, root: {
301 parent: {
302 list_node: {
303 "0": {
304 val: 1i64,
305 },
306 },
307 },
308 });
309
310 assert_matches!(list_node.adopt_entry(parent.clone_weak()), Err(Error::AdoptAncestor));
312 assert_data_tree!(inspector, root: {
313 parent: {
314 list_node: {
315 "0": {
316 val: 1i64,
317 },
318 },
319 },
320 });
321
322 let valid_node = inspector.root().create_child("valid");
324 valid_node.record_int("val", 2);
325 list_node.adopt_entry(valid_node).unwrap();
326 assert_data_tree!(inspector, root: {
327 parent: {
328 list_node: {
329 "1": {
330 val: 2i64,
331 },
332 },
333 },
334 });
335 }
336}