litcheck_lit/suite/
set.rs

1use std::{borrow::Borrow, fmt, path::Path, sync::Arc};
2
3use intrusive_collections::{intrusive_adapter, RBTreeAtomicLink};
4use serde::Deserialize;
5
6use super::TestSuite;
7
8pub type TestSuiteCursor<'a> = intrusive_collections::rbtree::Cursor<'a, TestSuiteAdapter>;
9
10intrusive_adapter!(pub TestSuiteAdapter = Arc<TestSuite>: TestSuite { link: RBTreeAtomicLink });
11impl<'a> intrusive_collections::KeyAdapter<'a> for TestSuiteAdapter {
12    type Key = TestSuiteKey;
13
14    #[inline]
15    fn get_key(&self, suite: &'a TestSuite) -> Self::Key {
16        suite.id()
17    }
18}
19
20#[derive(Default)]
21pub struct TestSuiteSet {
22    len: usize,
23    suites: intrusive_collections::RBTree<TestSuiteAdapter>,
24}
25impl TestSuiteSet {
26    pub fn is_empty(&self) -> bool {
27        self.suites.is_empty()
28    }
29
30    #[allow(unused)]
31    pub fn len(&self) -> usize {
32        self.len
33    }
34
35    pub fn iter(&self) -> Iter<'_> {
36        Iter::new(self.suites.front())
37    }
38
39    pub fn insert(&mut self, suite: Arc<TestSuite>) -> bool {
40        use intrusive_collections::rbtree::Entry;
41
42        match self.suites.entry(&suite.id()) {
43            Entry::Occupied(_) => {
44                log::debug!(
45                    "attempted to register multiple test suites with the same key: {:?}",
46                    suite.id()
47                );
48                false
49            }
50            Entry::Vacant(entry) => {
51                entry.insert(suite);
52                self.len += 1;
53                true
54            }
55        }
56    }
57
58    pub fn get<Q>(&self, key: &Q) -> Option<Arc<TestSuite>>
59    where
60        Q: Ord + ?Sized,
61        TestSuiteKey: Borrow<Q>,
62    {
63        let cursor = self.suites.find(key);
64        cursor.clone_pointer()
65    }
66
67    pub fn clear(&mut self) {
68        self.suites.clear();
69        self.len = 0;
70    }
71}
72
73pub struct Iter<'a> {
74    cursor: TestSuiteCursor<'a>,
75}
76impl<'a> Iter<'a> {
77    pub fn new(cursor: TestSuiteCursor<'a>) -> Self {
78        Self { cursor }
79    }
80}
81impl<'a> core::iter::FusedIterator for Iter<'a> {}
82impl<'a> Iterator for Iter<'a> {
83    type Item = Arc<TestSuite>;
84
85    fn next(&mut self) -> Option<Self::Item> {
86        let suite = self.cursor.clone_pointer();
87        if suite.is_some() {
88            self.cursor.move_next();
89        }
90        suite
91    }
92}
93
94#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize)]
95pub struct TestSuiteKey {
96    /// The name of the test suite, may not be empty.
97    name: toml::Spanned<Arc<str>>,
98    /// The source path from which the test suite configuration was loaded
99    #[serde(skip, default)]
100    pub path: Option<Arc<Path>>,
101}
102impl litcheck::diagnostics::Spanned for TestSuiteKey {
103    fn span(&self) -> litcheck::diagnostics::SourceSpan {
104        litcheck::diagnostics::SourceSpan::from(self.name.span())
105    }
106}
107impl TestSuiteKey {
108    pub fn new(name: toml::Spanned<Arc<str>>, path: Option<Arc<Path>>) -> Self {
109        Self { name, path }
110    }
111
112    /// The name of the suite to which this key is associated
113    #[inline]
114    pub fn name(&self) -> &str {
115        self.name.as_ref()
116    }
117
118    /// The path to the configuration file for the suite to which this key is associated
119    pub fn path(&self) -> &Path {
120        self.path.as_deref().unwrap_or(Path::new(""))
121    }
122}
123impl fmt::Display for TestSuiteKey {
124    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
125        let name = self.name();
126        let path = self.path();
127        match std::env::current_dir() {
128            Ok(cwd) => {
129                let path = path.strip_prefix(cwd).unwrap_or(path);
130                write!(f, "{name} @ {}", path.display())
131            }
132            Err(_) => {
133                write!(f, "{name} @ {}", path.display())
134            }
135        }
136    }
137}