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}