Skip to main content

terminus_store/storage/
directory.rs

1//! Directory-based implementation of storage traits.
2
3use 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            // ensure the underlying directory record is properly synchronized
100            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            // all good, let's a go
276            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        // We're not locking here to remove the file. The assumption
291        // is that any concurrent operation that is done on the label
292        // file will not matter. If it is a label read, a concurrent
293        // operation will simply get the label contents, which
294        // immediately afterwards become invalid. Similarly if it is
295        // for a write, the write will appear to be succesful even
296        // though the file will be gone afterwards. This is
297        // indistinguishable from the case where the read/write and
298        // the remove happened in reverse order.
299        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
309/// A version of the directory label store that keeps all labels in
310/// memory and doesn't lock.
311///
312/// This is useful for situations where we can be sure that only one
313/// process is working with a set of label files. In that case, we can
314/// keep all label files cached in memory in order to process reads as
315/// quickly as possible, and we can perform writes without any sort of
316/// file system locking.
317pub struct CachedDirectoryLabelStore {
318    path: PathBuf,
319    labels: Arc<RwLock<HashMap<String, Label>>>,
320}
321
322impl CachedDirectoryLabelStore {
323    /// Open a new label store.
324    ///
325    /// This will read in all label files on startup, which is why
326    /// this is an async operation.
327    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                // all good, let's a go
425                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}