pwd_grp/
mock.rs

1//! Mock provider of passwd/group data
2//!
3//! This affects **only the methods on [`PwdGrpProvider`]**,
4//! not the plain functions in the `pwd-grp` crate toplevel.
5
6use super::*;
7
8use std::sync::{Arc, Mutex, MutexGuard};
9
10/// Mock provider of passwd/group data
11///
12/// An implementor of [`PwdGrpProvider`]
13/// which doesn't look at the real system databases.
14///
15/// The [`Default`] contains just dummy entries for `root`.
16///
17/// You can pre-program it with passwd and group data.
18///
19/// The mock provider has interior mutability,
20/// so the trait can take non`-mut` references.
21///
22/// Note: this affects **only the methods on [`PwdGrpProvider`]**,
23/// not the plain functions in the `pwd-grp` crate toplevel.
24///
25/// ### Example
26///
27/// ```
28/// use pwd_grp::Group;
29/// use pwd_grp::mock::MockPwdGrpProvider;
30/// use pwd_grp::PwdGrpProvider as _;
31///
32/// let mock = MockPwdGrpProvider::new();
33//
34// What a palaver.  We can't just apply #[rustversion] to the asssert,
35// since "custom attributes cannot be applied to statements"
36// (rust-lang/rust/issues/54727, `#![feature(proc_macro_hygiene)]
37/// # type M = MockPwdGrpProvider;
38/// # #[rustversion::not(since(1.63))] fn gen_check(mock: &M) {}
39/// # #[rustversion::since(1.63)] fn gen_check(mock: &M) {
40//
41/// assert_eq!(mock.getgrnam::<String>("root").unwrap().unwrap().gid, 0);
42//
43/// # }
44/// # gen_check(&mock);
45///
46/// mock.add_to_groups([Group {
47///     name: "wombats".to_string(),
48///     passwd: "*".to_string(),
49///     gid: 42,
50///     mem: vec!["alice".to_string()].into(),
51///     ..Group::blank()
52/// }]);
53/// let grent: Group = mock.getgrgid(42).unwrap().unwrap();
54/// assert_eq!(grent.passwd, "*");
55/// ```
56#[derive(Debug, Clone, Default)]
57pub struct MockPwdGrpProvider(Arc<Mutex<Data>>);
58
59/// Real, effective and saved set-uid ids (uids or gids)
60#[derive(
61    Deftly, Debug, Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd, Hash,
62)]
63#[derive_deftly_adhoc]
64pub struct RealEffectiveSavedIds {
65    pub r: Id,
66    pub e: Id,
67    pub s: Id,
68}
69
70derive_deftly_adhoc! {
71    RealEffectiveSavedIds:
72    impl From<Id> for RealEffectiveSavedIds {
73        fn from(id: Id) -> Self {
74            RealEffectiveSavedIds {
75                $( $fname: id, )
76            }
77        }
78    }
79}
80
81/// Data
82#[derive(Debug, Clone, Deftly)]
83// Not really pub - not exported, needs to be here because
84// it's part of SealedProvider
85#[derive_deftly_adhoc]
86pub(crate) struct Data {
87    #[deftly(iter)]
88    pub(crate) passwd: Vec<Passwd<RawSafe>>,
89    #[deftly(iter)]
90    pub(crate) group: Vec<Group<RawSafe>>,
91    pub(crate) uids: RealEffectiveSavedIds,
92    pub(crate) gids: RealEffectiveSavedIds,
93    pub(crate) supplementary_groups: Vec<Id>,
94}
95
96impl Default for Data {
97    fn default() -> Self {
98        let s = |s: &str| s.to_string().into_bytes().into_boxed_slice();
99        Data {
100            passwd: vec![Passwd {
101                name: s("root"),
102                passwd: s("*"),
103                uid: 0,
104                gid: 0,
105                gecos: s("root"),
106                dir: s("/root"),
107                shell: s("/bin/bash"),
108                ..Passwd::blank()
109            }],
110            group: vec![Group {
111                name: s("root"),
112                passwd: s("x"),
113                gid: 0,
114                mem: vec![].into(),
115                ..Group::blank()
116            }],
117            uids: 1000.into(),
118            gids: 1000.into(),
119            supplementary_groups: vec![],
120        }
121    }
122}
123
124impl From<Data> for MockPwdGrpProvider {
125    fn from(data: Data) -> MockPwdGrpProvider {
126        MockPwdGrpProvider(Arc::new(Mutex::new(data)))
127    }
128}
129
130impl MockPwdGrpProvider {
131    /// Create a new `MockPwdGrpProvider` containing dummy entries for `root`
132    pub fn new() -> Self {
133        MockPwdGrpProvider::default()
134    }
135
136    fn lock(&self) -> MutexGuard<Data> {
137        self.0.lock().expect("lock poisoned")
138    }
139}
140
141impl SealedProvider for MockPwdGrpProvider {
142    fn with_mocks<R>(&self, f: impl FnOnce(SealedData) -> R) -> Option<R> {
143        Some(f(SealedData(&self.lock())))
144    }
145}
146impl PwdGrpProvider for MockPwdGrpProvider {}
147
148derive_deftly_adhoc! {
149    Data expect items:
150
151    impl MockPwdGrpProvider {
152
153        /// Create a new empty `MockPwdGrpProvider`
154        ///
155        /// All lookups will return `None`
156        pub fn new_empty() -> Self {
157            Data {
158                $( $fname: Default::default(), )
159            }.into()
160        }
161
162        $( ${select1 fmeta(iter) {
163            /// Append entries to the mock
164            #[doc = stringify!($fname)]
165            /// database
166            pub fn ${paste add_to_ $fname s}<I>(
167                &self,
168                data: impl IntoIterator<Item=I>,
169            )
170            where I: Into<${pascal_case $fname}<RawSafe>>,
171            {
172                self.lock().$fname.extend(
173                    data.into_iter().map(|ent| ent.into())
174                )
175            }
176
177        } else {
178
179            /// Set the mock
180            #[doc = stringify!($fname)]
181            pub fn ${paste set_ $fname}(
182                &self,
183                $fname: $ftype,
184            ) {
185                self.lock().$fname = $fname;
186            }
187
188        }} )
189    }
190}