1mod clangwrap;
2mod generate;
3mod headerpath;
4mod log;
5mod model;
6pub mod naming;
7
8use clangwrap::ClangWrap;
9use headerpath::header_include_path;
10use std::path::{Path, PathBuf};
11
12#[derive(thiserror::Error, Debug, PartialEq)]
13pub enum MocksmithError {
14 #[error("Another thread is already using Mocksmith")]
15 Busy,
16 #[error("Another thread using Mocksmith panicked")]
17 Poisoned,
18 #[error("Could not access Clang: {0}")]
19 ClangError(String),
20 #[error("Invalid sed style replacement string: {0}")]
21 InvalidSedReplacement(String),
22 #[error("Invalid regex string: {0}")]
23 InvalidRegex(String),
24 #[error("Input header file does not exist or is not a file: {0}")]
25 InputFileError(PathBuf),
26 #[error("Parse error {}at line {}, column {}: {}",
27 if file.is_none() {
28 String::new()
29 }
30 else {
31 format!("in file {} ", file.as_ref().unwrap().display())
32 },
33 line, column, message)]
34 ParseError {
35 message: String,
36 file: Option<PathBuf>,
37 line: u32,
38 column: u32,
39 },
40 #[error("No appropriate class to mock was found in the file")]
41 NothingToMock,
42}
43
44pub type Result<T> = std::result::Result<T, MocksmithError>;
45
46#[derive(Clone, Copy, Debug)]
48pub enum MethodsToMockStrategy {
49 All,
51 AllVirtual,
53 OnlyPureVirtual,
55}
56
57#[derive(Debug, PartialEq)]
59pub struct Mock {
60 pub source_file: Option<PathBuf>,
62 pub parent_name: String,
64 pub name: String,
66 pub code: String,
68}
69
70#[derive(Debug, PartialEq)]
72pub struct MockHeader {
73 pub mocks: Vec<Mock>,
75 pub code: String,
77}
78
79impl crate::MockHeader {
80 fn new() -> Self {
81 Self {
82 mocks: Vec::new(),
83 code: String::new(),
84 }
85 }
86}
87
88pub struct Mocksmith {
90 clangwrap: ClangWrap,
91 generator: generate::Generator,
92
93 include_paths: Vec<PathBuf>,
94 methods_to_mock: MethodsToMockStrategy,
95 filter_class: Box<dyn Fn(&str) -> bool>,
96 name_mock: Box<dyn Fn(&str) -> String>,
97}
98
99impl Mocksmith {
100 pub fn new(log_write: Option<Box<dyn std::io::Write>>, verbose: bool) -> Result<Self> {
105 let log = log_write.map(|write| log::Logger::new(write, verbose));
106 Self::create(ClangWrap::new(log)?)
107 }
108
109 pub fn new_when_available() -> Result<Self> {
115 let mut clangwrap = ClangWrap::blocking_new();
116 while let Err(MocksmithError::Poisoned) = clangwrap {
117 ClangWrap::clear_poison();
118 clangwrap = ClangWrap::blocking_new();
119 }
120 Self::create(clangwrap?)
121 }
122
123 fn create(clangwrap: clangwrap::ClangWrap) -> Result<Self> {
124 let methods_to_mock = MethodsToMockStrategy::AllVirtual;
125 let mocksmith = Self {
126 clangwrap,
127 generator: generate::Generator::new(methods_to_mock),
128 include_paths: Vec::new(),
129 methods_to_mock,
130 filter_class: Box::new(|_| true),
131 name_mock: Box::new(naming::default_name_mock),
132 };
133 Ok(mocksmith)
134 }
135
136 pub fn include_path<P>(mut self, include_path: P) -> Self
139 where
140 P: AsRef<Path>,
141 {
142 self.include_paths.push(include_path.as_ref().to_path_buf());
143 self
144 }
145
146 pub fn include_paths(mut self, include_paths: &[PathBuf]) -> Self {
149 self.include_paths.extend(include_paths.iter().cloned());
150 self
151 }
152
153 pub fn methods_to_mock(mut self, methods: MethodsToMockStrategy) -> Self {
156 self.methods_to_mock = methods;
157 self.generator.methods_to_mock(methods);
158 self
159 }
160
161 pub fn class_filter_fun(mut self, filter: impl Fn(&str) -> bool + 'static) -> Self {
164 self.filter_class = Box::new(filter);
165 self
166 }
167
168 pub fn ignore_errors(mut self, value: bool) -> Self {
174 self.clangwrap.set_ignore_errors(value);
175 self
176 }
177
178 pub fn cpp_standard(mut self, standard: Option<String>) -> Self {
181 self.clangwrap.set_cpp_standard(standard);
182 self
183 }
184
185 pub fn additional_clang_args(mut self, args: Vec<String>) -> Self {
187 self.clangwrap.set_additional_clang_args(args);
188 self
189 }
190
191 pub fn parse_function_bodies(mut self, value: bool) -> Self {
193 self.clangwrap.set_parse_function_bodies(value);
194 self
195 }
196
197 pub fn msvc_allow_overriding_deprecated_methods(mut self, value: bool) -> Self {
201 self.generator.add_deprecation_pragma(value);
202 self
203 }
204
205 pub fn simplified_nested_namespaces(mut self, value: bool) -> Self {
208 self.generator.simplified_nested_namespaces(value);
209 self
210 }
211
212 pub fn indent_str(mut self, indent: String) -> Self {
214 self.generator.indent_str(indent);
215 self
216 }
217
218 pub fn mock_name_fun(mut self, name_mock: impl Fn(&str) -> String + 'static) -> Self {
220 self.name_mock = Box::new(name_mock);
221 self
222 }
223
224 pub fn create_mocks_for_file<P>(&self, file: P) -> Result<Vec<Mock>>
227 where
228 P: AsRef<Path>,
229 {
230 if !file.as_ref().is_file() {
231 return Err(MocksmithError::InputFileError(file.as_ref().to_path_buf()));
232 }
233 self.clangwrap
234 .with_tu_from_file(&self.include_paths, file.as_ref(), |tu| {
235 let mut mocks = self.create_mocks(tu)?;
236 mocks.iter_mut().for_each(|m| {
237 m.source_file = Some(file.as_ref().to_path_buf());
238 });
239 Ok(mocks)
240 })
241 }
242
243 pub fn create_mocks_from_string(&self, content: &str) -> Result<Vec<Mock>> {
246 self.clangwrap
247 .with_tu_from_string(&self.include_paths, content, |tu| self.create_mocks(tu))
248 }
249
250 pub fn create_mock_header_for_files<P>(&self, files: &[P]) -> Result<MockHeader>
253 where
254 P: AsRef<Path>,
255 {
256 let source_file_include_paths: Vec<String> = files
257 .iter()
258 .map(|f| self.header_include_path(f.as_ref()))
259 .collect();
260
261 let mut header = MockHeader::new();
262 for file in files {
263 let mocks = self.create_mocks_for_file(file.as_ref())?;
264 header.mocks.extend(mocks);
265 }
266
267 header.code = self
268 .generator
269 .header(&source_file_include_paths, &header.mocks);
270
271 Ok(header)
272 }
273
274 fn header_include_path(&self, header_file: &Path) -> String {
275 if self.include_paths.is_empty() {
276 header_include_path(header_file, &[PathBuf::from(".")])
277 } else {
278 header_include_path(header_file, &self.include_paths)
279 }
280 }
281
282 fn create_mocks(&self, tu: &clang::TranslationUnit) -> Result<Vec<Mock>> {
283 let classes = model::classes_in_translation_unit(tu, self.methods_to_mock);
284 Ok(classes
285 .iter()
286 .filter(|class| (self.filter_class)(class.name.as_str()))
287 .map(|class| self.generator.mock(class, &self.mock_name(class)))
288 .collect())
289 }
290
291 fn mock_name(&self, class: &model::ClassToMock) -> String {
292 (self.name_mock)(&class.name)
293 }
294}
295
296#[cfg(test)]
297mod tests {
298 use super::*;
299
300 #[test]
301 fn test_new_with_threads() {
302 let mocksmith = Mocksmith::new(None, false).unwrap();
303
304 let handle = std::thread::spawn(|| {
305 assert!(matches!(
306 Mocksmith::new(None, false),
307 Err(MocksmithError::Busy)
308 ));
309 });
310 handle.join().unwrap();
311
312 let handle = std::thread::spawn(|| {
313 let _mocksmith = Mocksmith::new_when_available().unwrap();
314 });
315 std::thread::sleep(std::time::Duration::from_millis(25));
316 std::mem::drop(mocksmith);
317 handle.join().unwrap();
318 }
319}