1use locking::*;
4use std::collections::HashMap;
5use std::io::{self, SeekFrom};
6use std::path::PathBuf;
7use std::sync::Arc;
8use tokio::fs;
9use tokio::io::{AsyncReadExt, AsyncWriteExt};
10use tokio::sync::RwLock;
11
12use async_trait::async_trait;
13
14pub use tdb_succinct::storage::file::*;
15
16use super::*;
17
18const PREFIX_DIR_SIZE: usize = 3;
19
20#[derive(Clone)]
21pub struct DirectoryLayerStore {
22 path: PathBuf,
23}
24
25impl DirectoryLayerStore {
26 pub fn new<P: Into<PathBuf>>(path: P) -> DirectoryLayerStore {
27 DirectoryLayerStore { path: path.into() }
28 }
29}
30
31#[async_trait]
32impl PersistentLayerStore for DirectoryLayerStore {
33 type File = FileBackedStore;
34 async fn directories(&self) -> io::Result<Vec<[u32; 5]>> {
35 let mut stream = fs::read_dir(&self.path).await?;
36 let mut result = Vec::new();
37 while let Some(direntry) = stream.next_entry().await? {
38 if direntry.file_type().await?.is_dir() {
39 let os_name = direntry.file_name();
40 let name = os_name.to_str().ok_or_else(|| {
41 io::Error::new(
42 io::ErrorKind::InvalidData,
43 "unexpected non-utf8 directory name",
44 )
45 })?;
46 result.push(string_to_name(name)?);
47 }
48 }
49
50 Ok(result)
51 }
52
53 async fn create_named_directory(&self, name: [u32; 5]) -> io::Result<[u32; 5]> {
54 let mut p = self.path.clone();
55 let name_str = name_to_string(name);
56 p.push(&name_str[0..PREFIX_DIR_SIZE]);
57 p.push(name_str);
58
59 fs::create_dir_all(p).await?;
60
61 Ok(name)
62 }
63
64 async fn directory_exists(&self, name: [u32; 5]) -> io::Result<bool> {
65 let mut p = self.path.clone();
66 let name = name_to_string(name);
67 p.push(&name[0..PREFIX_DIR_SIZE]);
68 p.push(name);
69
70 match fs::metadata(p).await {
71 Ok(m) => Ok(m.is_dir()),
72 Err(_) => Ok(false),
73 }
74 }
75
76 async fn get_file(&self, directory: [u32; 5], name: &str) -> io::Result<Self::File> {
77 let mut p = self.path.clone();
78 let dir_name = name_to_string(directory);
79 p.push(&dir_name[0..PREFIX_DIR_SIZE]);
80 p.push(dir_name);
81 p.push(name);
82 Ok(FileBackedStore::new(p))
83 }
84
85 async fn file_exists(&self, directory: [u32; 5], file: &str) -> io::Result<bool> {
86 let mut p = self.path.clone();
87 let dir_name = name_to_string(directory);
88 p.push(&dir_name[0..PREFIX_DIR_SIZE]);
89 p.push(dir_name);
90 p.push(file);
91
92 match fs::metadata(p).await {
93 Ok(m) => Ok(m.is_file()),
94 Err(_) => Ok(false),
95 }
96 }
97 async fn finalize(&self, directory: [u32; 5]) -> io::Result<()> {
98 if cfg!(unix) {
99 let mut directory_path = self.path.clone();
101 let dir_name = name_to_string(directory);
102 directory_path.push(&dir_name[0..PREFIX_DIR_SIZE]);
103 directory_path.push(dir_name);
104
105 let mut options = tokio::fs::OpenOptions::new();
106 options.create(false);
107 options.read(true);
108 options.write(false);
109 let dir_fd = options.open(directory_path).await?;
110 dir_fd.sync_all().await?;
111 }
112
113 Ok(())
114 }
115}
116
117#[derive(Clone)]
118pub struct DirectoryLabelStore {
119 path: PathBuf,
120}
121
122impl DirectoryLabelStore {
123 pub fn new<P: Into<PathBuf>>(path: P) -> DirectoryLabelStore {
124 DirectoryLabelStore { path: path.into() }
125 }
126}
127
128fn get_label_from_data(name: String, data: &[u8]) -> io::Result<Label> {
129 let s = String::from_utf8_lossy(&data);
130 let lines: Vec<&str> = s.lines().collect();
131 if lines.len() != 2 {
132 return Err(io::Error::new(
133 io::ErrorKind::InvalidData,
134 format!(
135 "expected label file to have two lines. contents were ({:?})",
136 lines
137 ),
138 ));
139 }
140
141 let version_str = &lines[0];
142 let layer_str = &lines[1];
143
144 let version = u64::from_str_radix(version_str, 10);
145 if version.is_err() {
146 return Err(io::Error::new(
147 io::ErrorKind::InvalidData,
148 format!(
149 "expected first line of label file to be a number but it was {}",
150 version_str
151 ),
152 ));
153 }
154
155 if layer_str.is_empty() {
156 Ok(Label {
157 name,
158 layer: None,
159 version: version.unwrap(),
160 })
161 } else {
162 let layer = layer::string_to_name(layer_str)?;
163 Ok(Label {
164 name,
165 layer: Some(layer),
166 version: version.unwrap(),
167 })
168 }
169}
170
171async fn get_label_from_file<P: Into<PathBuf>>(path: P) -> io::Result<Label> {
172 let path: PathBuf = path.into();
173 let label = path.file_stem().unwrap().to_str().unwrap().to_owned();
174
175 let mut file = LockedFile::open(path).await?;
176 let mut data = Vec::new();
177 file.read_to_end(&mut data).await?;
178
179 get_label_from_data(label, &data)
180}
181
182async fn get_label_from_exclusive_locked_file<P: Into<PathBuf>>(
183 path: P,
184) -> io::Result<(Label, ExclusiveLockedFile)> {
185 let path: PathBuf = path.into();
186 let label = path.file_stem().unwrap().to_str().unwrap().to_owned();
187
188 let mut file = ExclusiveLockedFile::open(path).await?;
189 let mut data = Vec::new();
190 file.read_to_end(&mut data).await?;
191
192 let label = get_label_from_data(label, &data)?;
193 file.seek(SeekFrom::Start(0)).await?;
194
195 Ok((label, file))
196}
197
198#[async_trait]
199impl LabelStore for DirectoryLabelStore {
200 async fn labels(&self) -> io::Result<Vec<Label>> {
201 let mut stream = fs::read_dir(self.path.clone()).await?;
202 let mut result = Vec::new();
203 while let Some(direntry) = stream.next_entry().await? {
204 if direntry.file_type().await?.is_file() {
205 let os_name = direntry.file_name();
206 let name = os_name.to_str().ok_or_else(|| {
207 io::Error::new(
208 io::ErrorKind::InvalidData,
209 "unexpected non-utf8 directory name",
210 )
211 })?;
212 if name.ends_with(".label") {
213 let label = get_label_from_file(direntry.path()).await?;
214 result.push(label);
215 }
216 }
217 }
218
219 Ok(result)
220 }
221
222 async fn create_label(&self, label: &str) -> io::Result<Label> {
223 let mut p = self.path.clone();
224 p.push(format!("{}.label", label));
225 let contents = "0\n\n".to_string().into_bytes();
226 match fs::metadata(&p).await {
227 Ok(_) => Err(io::Error::new(
228 io::ErrorKind::InvalidInput,
229 "database already exists",
230 )),
231 Err(e) => match e.kind() {
232 io::ErrorKind::NotFound => {
233 let mut file = ExclusiveLockedFile::create_and_open(p).await?;
234 file.write_all(&contents).await?;
235 file.flush().await?;
236 file.sync_all().await?;
237
238 Ok(Label::new_empty(label))
239 }
240 _ => Err(e),
241 },
242 }
243 }
244
245 async fn get_label(&self, label: &str) -> io::Result<Option<Label>> {
246 let mut p = self.path.clone();
247 p.push(format!("{}.label", label));
248
249 match get_label_from_file(p).await {
250 Ok(label) => Ok(Some(label)),
251 Err(e) => match e.kind() {
252 io::ErrorKind::NotFound => Ok(None),
253 _ => Err(e),
254 },
255 }
256 }
257
258 async fn set_label_option(
259 &self,
260 label: &Label,
261 layer: Option<[u32; 5]>,
262 ) -> io::Result<Option<Label>> {
263 let new_label = label.with_updated_layer(layer);
264 let contents = match new_label.layer {
265 None => format!("{}\n\n", new_label.version).into_bytes(),
266 Some(layer) => {
267 format!("{}\n{}\n", new_label.version, layer::name_to_string(layer)).into_bytes()
268 }
269 };
270
271 let mut p = self.path.clone();
272 p.push(format!("{}.label", label.name));
273 let (retrieved_label, mut file) = get_label_from_exclusive_locked_file(p).await?;
274 if retrieved_label == *label {
275 file.truncate().await?;
277 file.write_all(&contents).await?;
278 file.flush().await?;
279 file.sync_all().await?;
280 Ok(Some(new_label))
281 } else {
282 Ok(None)
283 }
284 }
285
286 async fn delete_label(&self, name: &str) -> io::Result<bool> {
287 let mut p = self.path.clone();
288 p.push(format!("{}.label", name));
289
290 match tokio::fs::remove_file(p).await {
300 Ok(()) => Ok(true),
301 Err(e) => match e.kind() {
302 io::ErrorKind::NotFound => Ok(false),
303 _ => Err(e),
304 },
305 }
306 }
307}
308
309pub struct CachedDirectoryLabelStore {
318 path: PathBuf,
319 labels: Arc<RwLock<HashMap<String, Label>>>,
320}
321
322impl CachedDirectoryLabelStore {
323 pub async fn open<P: Into<PathBuf>>(path: P) -> io::Result<Self> {
328 let path: PathBuf = path.into();
329 let labels = get_all_labels_from_dir(&path).await?;
330
331 Ok(Self {
332 path,
333 labels: Arc::new(RwLock::new(labels)),
334 })
335 }
336}
337
338async fn get_all_labels_from_dir(p: &PathBuf) -> io::Result<HashMap<String, Label>> {
339 let mut result = HashMap::new();
340 let mut entries = tokio::fs::read_dir(p).await?;
341
342 while let Some(entry) = entries.next_entry().await? {
343 if !entry.file_type().await?.is_file() {
344 continue;
345 }
346 if let Ok(file_name) = entry.file_name().into_string() {
347 if !file_name.ends_with(".label") {
348 continue;
349 }
350
351 let label_name = file_name[..file_name.len() - 6].to_string();
352 let label = get_label_from_file(entry.path()).await?;
353
354 result.insert(label_name, label);
355 }
356 }
357
358 Ok(result)
359}
360
361#[async_trait]
362impl LabelStore for CachedDirectoryLabelStore {
363 async fn labels(&self) -> io::Result<Vec<Label>> {
364 let labels = self.labels.read().await;
365 Ok(labels.values().cloned().collect())
366 }
367
368 async fn create_label(&self, label: &str) -> io::Result<Label> {
369 let mut labels = self.labels.write().await;
370 if labels.contains_key(label) {
371 return Err(io::Error::new(
372 io::ErrorKind::InvalidInput,
373 "database already exists",
374 ));
375 }
376
377 let mut p = self.path.clone();
378 p.push(format!("{}.label", label));
379 let contents = b"0\n\n";
380 match fs::metadata(&p).await {
381 Ok(_) => Err(io::Error::new(
382 io::ErrorKind::Other,
383 "label was not in cached map but was found on disk",
384 )),
385 Err(e) => match e.kind() {
386 io::ErrorKind::NotFound => {
387 let mut options = fs::OpenOptions::new();
388 options.create_new(true);
389 options.write(true);
390 let mut file = options.open(p).await?;
391 file.write_all(contents).await?;
392 file.flush().await?;
393 file.sync_all().await?;
394
395 let l = Label::new_empty(label);
396 labels.insert(label.to_string(), l.clone());
397
398 Ok(l)
399 }
400 _ => Err(e),
401 },
402 }
403 }
404 async fn get_label(&self, label: &str) -> io::Result<Option<Label>> {
405 let labels = self.labels.read().await;
406 Ok(labels.get(label).cloned())
407 }
408 async fn set_label_option(
409 &self,
410 label: &Label,
411 layer: Option<[u32; 5]>,
412 ) -> io::Result<Option<Label>> {
413 let new_label = label.with_updated_layer(layer);
414 let contents = match new_label.layer {
415 None => format!("{}\n\n", new_label.version).into_bytes(),
416 Some(layer) => {
417 format!("{}\n{}\n", new_label.version, layer::name_to_string(layer)).into_bytes()
418 }
419 };
420
421 let mut labels = self.labels.write().await;
422 if let Some(retrieved_label) = labels.get(&label.name) {
423 if retrieved_label == label {
424 let mut p = self.path.clone();
426 p.push(format!("{}.label", label.name));
427 let mut options = fs::OpenOptions::new();
428 options.create(false);
429 options.write(true);
430 let mut file = options.open(p).await?;
431 file.write_all(&contents).await?;
432 file.flush().await?;
433 file.sync_data().await?;
434
435 labels.insert(label.name.clone(), new_label.clone());
436 Ok(Some(new_label))
437 } else {
438 Ok(None)
439 }
440 } else {
441 Err(io::Error::new(io::ErrorKind::NotFound, "label not found"))
442 }
443 }
444
445 async fn delete_label(&self, name: &str) -> io::Result<bool> {
446 let mut labels = self.labels.write().await;
447 if labels.remove(name).is_some() {
448 let mut p = self.path.clone();
449 p.push(format!("{}.label", name));
450 tokio::fs::remove_file(p).await?;
451
452 Ok(true)
453 } else {
454 Ok(false)
455 }
456 }
457}
458
459#[cfg(test)]
460mod tests {
461 use super::*;
462 use crate::layer::*;
463 use tempfile::tempdir;
464
465 #[tokio::test]
466 async fn write_and_read_file_backed() {
467 let dir = tempdir().unwrap();
468 let file_path = dir.path().join("foo");
469 let file = FileBackedStore::new(file_path);
470
471 let mut w = file.open_write().await.unwrap();
472 w.write_all(&[1, 2, 3]).await.unwrap();
473 w.flush().await.unwrap();
474 let mut buf = Vec::new();
475 file.open_read()
476 .await
477 .unwrap()
478 .read_to_end(&mut buf)
479 .await
480 .unwrap();
481
482 assert_eq!(vec![1, 2, 3], buf);
483 }
484
485 #[tokio::test]
486 async fn write_and_map_file_backed() {
487 let dir = tempdir().unwrap();
488 let file_path = dir.path().join("foo");
489 let file = FileBackedStore::new(file_path);
490
491 let mut w = file.open_write().await.unwrap();
492 w.write_all(&[1, 2, 3]).await.unwrap();
493 w.flush().await.unwrap();
494
495 let map = file.map().await.unwrap();
496
497 assert_eq!(&vec![1, 2, 3][..], &map.as_ref()[..]);
498 }
499
500 #[tokio::test]
501 async fn write_and_map_large_file_backed() {
502 let dir = tempdir().unwrap();
503 let file_path = dir.path().join("foo");
504 let file = FileBackedStore::new(file_path);
505
506 let mut w = file.open_write().await.unwrap();
507 let mut contents = vec![0u8; 4096 << 4];
508 for i in 0..contents.capacity() {
509 contents[i] = (i as usize % 256) as u8;
510 }
511
512 w.write_all(&contents).await.unwrap();
513 w.flush().await.unwrap();
514
515 let map = file.map().await.unwrap();
516
517 assert_eq!(contents, map.as_ref());
518 }
519
520 #[tokio::test]
521 async fn create_layers_from_directory_store() {
522 let dir = tempdir().unwrap();
523 let store = DirectoryLayerStore::new(dir.path());
524
525 let layer = async {
526 let mut builder = store.create_base_layer().await?;
527 let base_name = builder.name();
528
529 builder.add_value_triple(ValueTriple::new_string_value("cow", "says", "moo"));
530 builder.add_value_triple(ValueTriple::new_string_value("pig", "says", "oink"));
531 builder.add_value_triple(ValueTriple::new_string_value("duck", "says", "quack"));
532
533 builder.commit_boxed().await?;
534
535 let mut builder = store.create_child_layer(base_name).await?;
536 let child_name = builder.name();
537
538 builder.remove_value_triple(ValueTriple::new_string_value("duck", "says", "quack"));
539 builder.add_value_triple(ValueTriple::new_node("cow", "likes", "pig"));
540
541 builder.commit_boxed().await?;
542
543 store.get_layer(child_name).await
544 }
545 .await
546 .unwrap()
547 .unwrap();
548
549 assert!(layer.value_triple_exists(&ValueTriple::new_string_value("cow", "says", "moo")));
550 assert!(layer.value_triple_exists(&ValueTriple::new_string_value("pig", "says", "oink")));
551 assert!(layer.value_triple_exists(&ValueTriple::new_node("cow", "likes", "pig")));
552 assert!(!layer.value_triple_exists(&ValueTriple::new_string_value("duck", "says", "quack")));
553 }
554
555 #[tokio::test]
556 async fn directory_create_and_retrieve_equal_label() {
557 let dir = tempdir().unwrap();
558 let store = DirectoryLabelStore::new(dir.path());
559
560 let (stored, retrieved) = async {
561 let stored = store.create_label("foo").await?;
562 let retrieved = store.get_label("foo").await?;
563
564 Ok::<_, io::Error>((stored, retrieved))
565 }
566 .await
567 .unwrap();
568
569 assert_eq!(None, stored.layer);
570 assert_eq!(stored, retrieved.unwrap());
571 }
572
573 #[tokio::test]
574 async fn directory_update_label_succeeds() {
575 let dir = tempdir().unwrap();
576 let store = DirectoryLabelStore::new(dir.path());
577
578 let retrieved = async {
579 let stored = store.create_label("foo").await?;
580 store.set_label(&stored, [6, 7, 8, 9, 10]).await?;
581
582 store.get_label("foo").await
583 }
584 .await
585 .unwrap()
586 .unwrap();
587
588 assert_eq!(Some([6, 7, 8, 9, 10]), retrieved.layer);
589 }
590
591 #[tokio::test]
592 async fn directory_update_label_twice_from_same_label_object_fails() {
593 let dir = tempdir().unwrap();
594 let store = DirectoryLabelStore::new(dir.path());
595
596 let (stored2, stored3) = async {
597 let stored1 = store.create_label("foo").await?;
598
599 let stored2 = store.set_label(&stored1, [6, 7, 8, 9, 10]).await?;
600 let stored3 = store.set_label(&stored1, [10, 9, 8, 7, 6]).await?;
601
602 Ok::<_, io::Error>((stored2, stored3))
603 }
604 .await
605 .unwrap();
606
607 assert!(stored2.is_some());
608 assert!(stored3.is_none());
609 }
610
611 #[tokio::test]
612 async fn directory_create_label_twice_errors() {
613 let dir = tempdir().unwrap();
614 let store = DirectoryLabelStore::new(dir.path());
615
616 store.create_label("foo").await.unwrap();
617 let result = store.create_label("foo").await;
618
619 assert!(result.is_err());
620
621 let error = result.err().unwrap();
622 assert_eq!(io::ErrorKind::InvalidInput, error.kind());
623 }
624
625 #[tokio::test]
626 async fn nonexistent_file_is_nonexistent() {
627 let file = FileBackedStore::new("asdfasfopivbuzxcvopiuvpoawehkafpouzvxv");
628 assert!(!file.exists().await.unwrap());
629 }
630
631 #[tokio::test]
632 async fn rollup_and_retrieve_base() {
633 let dir = tempdir().unwrap();
634 let store = Arc::new(DirectoryLayerStore::new(dir.path()));
635
636 let mut builder = store.create_base_layer().await.unwrap();
637 let base_name = builder.name();
638
639 builder.add_value_triple(ValueTriple::new_string_value("cow", "says", "moo"));
640 builder.add_value_triple(ValueTriple::new_string_value("pig", "says", "oink"));
641 builder.add_value_triple(ValueTriple::new_string_value("duck", "says", "quack"));
642
643 builder.commit_boxed().await.unwrap();
644
645 let mut builder = store.create_child_layer(base_name).await.unwrap();
646 let child_name = builder.name();
647
648 builder.remove_value_triple(ValueTriple::new_string_value("duck", "says", "quack"));
649 builder.add_value_triple(ValueTriple::new_node("cow", "likes", "pig"));
650
651 builder.commit_boxed().await.unwrap();
652
653 let unrolled_layer = store.get_layer(child_name).await.unwrap().unwrap();
654
655 let _rolled_id = store.clone().rollup(unrolled_layer).await.unwrap();
656 let rolled_layer = store.get_layer(child_name).await.unwrap().unwrap();
657
658 match *rolled_layer {
659 InternalLayer::Rollup(_) => {}
660 _ => panic!("not a rollup"),
661 }
662
663 assert!(
664 rolled_layer.value_triple_exists(&ValueTriple::new_string_value("cow", "says", "moo"))
665 );
666 assert!(
667 rolled_layer.value_triple_exists(&ValueTriple::new_string_value("pig", "says", "oink"))
668 );
669 assert!(rolled_layer.value_triple_exists(&ValueTriple::new_node("cow", "likes", "pig")));
670 assert!(!rolled_layer
671 .value_triple_exists(&ValueTriple::new_string_value("duck", "says", "quack")));
672 }
673
674 #[tokio::test]
675 async fn rollup_and_retrieve_child() {
676 let dir = tempdir().unwrap();
677 let store = Arc::new(DirectoryLayerStore::new(dir.path()));
678
679 let mut builder = store.create_base_layer().await.unwrap();
680 let base_name = builder.name();
681
682 builder.add_value_triple(ValueTriple::new_string_value("cow", "says", "moo"));
683 builder.add_value_triple(ValueTriple::new_string_value("pig", "says", "oink"));
684 builder.add_value_triple(ValueTriple::new_string_value("duck", "says", "quack"));
685
686 builder.commit_boxed().await.unwrap();
687
688 let mut builder = store.create_child_layer(base_name).await.unwrap();
689 let child_name = builder.name();
690
691 builder.remove_value_triple(ValueTriple::new_string_value("duck", "says", "quack"));
692 builder.add_value_triple(ValueTriple::new_node("cow", "likes", "pig"));
693
694 builder.commit_boxed().await.unwrap();
695
696 let mut builder = store.create_child_layer(child_name).await.unwrap();
697 let child_name = builder.name();
698
699 builder.remove_value_triple(ValueTriple::new_string_value("cow", "likes", "pig"));
700 builder.add_value_triple(ValueTriple::new_node("cow", "hates", "pig"));
701
702 builder.commit_boxed().await.unwrap();
703
704 let unrolled_layer = store.get_layer(child_name).await.unwrap().unwrap();
705
706 let _rolled_id = store
707 .clone()
708 .rollup_upto(unrolled_layer, base_name)
709 .await
710 .unwrap();
711 let rolled_layer = store.get_layer(child_name).await.unwrap().unwrap();
712
713 match *rolled_layer {
714 InternalLayer::Rollup(_) => {}
715 _ => panic!("not a rollup"),
716 }
717
718 assert!(
719 rolled_layer.value_triple_exists(&ValueTriple::new_string_value("cow", "says", "moo"))
720 );
721 assert!(
722 rolled_layer.value_triple_exists(&ValueTriple::new_string_value("pig", "says", "oink"))
723 );
724 assert!(rolled_layer.value_triple_exists(&ValueTriple::new_node("cow", "hates", "pig")));
725 assert!(!rolled_layer
726 .value_triple_exists(&ValueTriple::new_string_value("cow", "likes", "pig")));
727 assert!(!rolled_layer
728 .value_triple_exists(&ValueTriple::new_string_value("duck", "says", "quack")));
729 }
730
731 #[tokio::test]
732 async fn create_and_delete_label() {
733 let dir = tempdir().unwrap();
734 let store = DirectoryLabelStore::new(dir.path());
735
736 store.create_label("foo").await.unwrap();
737 assert!(store.get_label("foo").await.unwrap().is_some());
738 assert!(store.delete_label("foo").await.unwrap());
739 assert!(store.get_label("foo").await.unwrap().is_none());
740 }
741
742 #[tokio::test]
743 async fn delete_nonexistent_label() {
744 let dir = tempdir().unwrap();
745 let store = DirectoryLabelStore::new(dir.path());
746
747 assert!(!store.delete_label("foo").await.unwrap());
748 }
749
750 #[tokio::test]
751 async fn delete_shared_locked_label() {
752 let dir = tempdir().unwrap();
753 let store = DirectoryLabelStore::new(dir.path());
754
755 store.create_label("foo").await.unwrap();
756 let label_path = dir.path().join("foo.label");
757 let _f = LockedFile::open(label_path).await.unwrap();
758
759 assert!(store.delete_label("foo").await.unwrap());
760 }
761
762 #[tokio::test]
763 async fn delete_exclusive_locked_label() {
764 let dir = tempdir().unwrap();
765 let store = DirectoryLabelStore::new(dir.path());
766
767 store.create_label("foo").await.unwrap();
768 let label_path = dir.path().join("foo.label");
769 let _f = ExclusiveLockedFile::open(label_path).await.unwrap();
770
771 assert!(store.delete_label("foo").await.unwrap());
772 }
773}