1use std::{fs, io::BufReader, path::Path};
2
3use serde::{Deserialize, Serialize};
4
5use crate::mutation_tool::MutationOperators;
6
7#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, Clone)]
8pub struct MutantKrakenConfig {
9 pub general: GeneralConfig,
10 pub ignore: IgnoreConfig,
11 pub threading: ThreadingConfig,
12 pub output: OutputConfig,
13 pub logging: LoggingConfig,
14}
15
16#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
17pub struct GeneralConfig {
18 pub timeout: Option<u64>,
21
22 #[serde(default)]
23 pub operators: Vec<MutationOperators>,
24}
25
26impl Default for GeneralConfig {
27 fn default() -> Self {
29 use MutationOperators::*;
30 Self {
31 timeout: None,
32 operators: vec![
33 ArithmeticReplacementOperator,
34 UnaryRemovalOperator,
35 LogicalReplacementOperator,
36 RelationalReplacementOperator,
37 AssignmentReplacementOperator,
38 UnaryReplacementOperator,
39 NotNullAssertionOperator,
40 ElvisRemoveOperator,
41 ElvisLiteralChangeOperator,
42 LiteralChangeOperator,
43 ExceptionChangeOperator,
44 WhenRemoveBranchOperator,
45 RemoveLabelOperator,
46 FunctionalBinaryReplacementOperator,
47 FunctionalReplacementOperator,
48 ],
49 }
50 }
51}
52
53#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
54pub struct LoggingConfig {
55 pub log_level: String,
56}
57
58#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
59pub struct IgnoreConfig {
60 pub ignore_files: Vec<String>,
61 pub ignore_directories: Vec<String>,
62}
63
64#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
65pub struct ThreadingConfig {
66 pub max_threads: usize,
67}
68
69#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, Clone)]
70pub struct OutputConfig {
71 pub display_end_table: bool,
72}
73
74impl MutantKrakenConfig {
75 pub fn new() -> Self {
76 Self::default()
77 }
78
79 pub fn load_config<P: AsRef<Path>>(path: P) -> Self {
80 let mut config = match fs::File::open(path.as_ref().join("mutantkraken.config.json")) {
81 Ok(file) => {
82 let buffer_reader = BufReader::new(file);
83 match serde_json::from_reader(buffer_reader) {
84 Ok(config) => config,
85 Err(e) => {
86 println!("[WARNING] ⚠️ Could not parse config file, view logs for error.");
87 tracing::warn!(
88 "Could not parse config file, using default config. Error: {}",
89 e
90 );
91 Self::default()
92 }
93 }
94 }
95 Err(_) => {
96 println!("[WARNING] ⚠️ Could not find mutantkraken.config.json file in root directory, using default config.");
97 Self::default()
98 }
99 };
100
101 if config.general.operators.is_empty() {
102 println!("[WARNING] ⚠️ No mutation operators specified in config file, using default operators.");
103 config.general.operators = GeneralConfig::default().operators;
104 }
105
106 config
107 }
108}
109
110impl Default for IgnoreConfig {
111 fn default() -> Self {
112 Self {
113 ignore_files: vec![
114 r#"^.*Test\.[^.]*$"#.to_string(), ],
116 ignore_directories: vec![
117 "dist".into(),
118 "build".into(),
119 "bin".into(),
120 ".gradle".into(),
121 ".idea".into(),
122 "gradle".into(),
123 ],
124 }
125 }
126}
127
128impl Default for ThreadingConfig {
129 fn default() -> Self {
130 Self { max_threads: 30 }
131 }
132}
133
134impl Default for LoggingConfig {
135 fn default() -> Self {
136 Self {
137 log_level: "info".into(),
138 }
139 }
140}
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use std::{env::temp_dir, fs::File, io::Write};
145 use MutationOperators::*;
146 #[test]
147 fn test_default_general_config() {
148 let default_general = GeneralConfig::default();
149 assert_eq!(default_general.timeout, None);
150 assert_eq!(
151 default_general.operators,
152 vec![
153 ArithmeticReplacementOperator,
154 UnaryRemovalOperator,
155 LogicalReplacementOperator,
156 RelationalReplacementOperator,
157 AssignmentReplacementOperator,
158 UnaryReplacementOperator,
159 NotNullAssertionOperator,
160 ElvisRemoveOperator,
161 ElvisLiteralChangeOperator,
162 LiteralChangeOperator,
163 ExceptionChangeOperator,
164 WhenRemoveBranchOperator,
165 RemoveLabelOperator,
166 FunctionalBinaryReplacementOperator,
167 FunctionalReplacementOperator,
168 ]
169 );
170 }
171
172 #[test]
173 fn test_default_ignore_config() {
174 let default_ignore = IgnoreConfig::default();
175 assert_eq!(default_ignore.ignore_files.len(), 1);
176 assert_eq!(default_ignore.ignore_directories.len(), 6);
177 }
178
179 #[test]
180 fn test_default_threading_config() {
181 let default_threading = ThreadingConfig::default();
182 assert_eq!(default_threading.max_threads, 30);
183 }
184
185 #[test]
186 fn test_default_logging_config() {
187 let default_logging = LoggingConfig::default();
188 assert_eq!(default_logging.log_level, "info");
189 }
190
191 #[test]
192 fn test_default_output_config() {
193 let default_output = OutputConfig::default();
194 assert_eq!(default_output.display_end_table, false);
195 }
196
197 #[test]
198 fn test_new_mutantkraken_config() {
199 let config = MutantKrakenConfig::new();
200 assert_eq!(config.general.timeout, None);
201 assert_eq!(
202 config.general.operators,
203 vec![
204 ArithmeticReplacementOperator,
205 UnaryRemovalOperator,
206 LogicalReplacementOperator,
207 RelationalReplacementOperator,
208 AssignmentReplacementOperator,
209 UnaryReplacementOperator,
210 NotNullAssertionOperator,
211 ElvisRemoveOperator,
212 ElvisLiteralChangeOperator,
213 LiteralChangeOperator,
214 ExceptionChangeOperator,
215 WhenRemoveBranchOperator,
216 RemoveLabelOperator,
217 FunctionalBinaryReplacementOperator,
218 FunctionalReplacementOperator,
219 ]
220 );
221 assert_eq!(config.ignore.ignore_files.len(), 1);
222 assert_eq!(config.ignore.ignore_directories.len(), 6);
223 assert_eq!(config.threading.max_threads, 30);
224 assert_eq!(config.output.display_end_table, false);
225 assert_eq!(config.logging.log_level, "info");
226 }
227
228 #[test]
229 fn test_load_config_from_valid_file() {
230 let temp_dir = temp_dir();
231 let file_path = temp_dir.join("mutantkraken.config.json");
232 let mut file = File::create(file_path).expect("Failed to create temporary file");
233
234 let config = MutantKrakenConfig {
236 general: GeneralConfig {
237 timeout: Some(10),
238 operators: vec![
239 MutationOperators::UnaryRemovalOperator,
240 MutationOperators::AssignmentReplacementOperator,
241 ],
242 },
243 ignore: IgnoreConfig {
244 ignore_files: vec!["file1".into(), "file2".into()],
245 ignore_directories: vec!["dir1".into(), "dir2".into()],
246 },
247 threading: ThreadingConfig { max_threads: 42 },
248 output: OutputConfig {
249 display_end_table: true,
250 },
251 logging: LoggingConfig {
252 log_level: "debug".into(),
253 },
254 };
255
256 let config_json = serde_json::to_string_pretty(&config).unwrap();
257 file.write_all(config_json.as_bytes())
258 .expect("Failed to write to temporary file");
259
260 let config = MutantKrakenConfig::load_config(temp_dir);
261 assert_eq!(config.general.timeout, Some(10));
262 assert_eq!(
263 config.general.operators,
264 vec![
265 MutationOperators::UnaryRemovalOperator,
266 MutationOperators::AssignmentReplacementOperator
267 ]
268 );
269 assert_eq!(config.ignore.ignore_files, vec!["file1", "file2"]);
270 assert_eq!(config.ignore.ignore_directories, vec!["dir1", "dir2"]);
271 assert_eq!(config.threading.max_threads, 42);
272 assert_eq!(config.output.display_end_table, true);
273 assert_eq!(config.logging.log_level, "debug");
274 }
275
276 #[test]
277 fn test_load_config_from_invalid_file() {
278 let temp_dir = temp_dir().join("invalid_config");
280 fs::create_dir_all(&temp_dir).expect("Failed to create temporary directory");
282 let file_path = temp_dir.join("mutantkraken.config.json");
284 let mut file = File::create(file_path).expect("Failed to create temporary file");
285
286 let invalid_json_content = r#"
287 {
288 "general": {
289 "timeout": "invalid_timeout_value"
290 }
291 }
292 "#;
293
294 writeln!(file, "{}", invalid_json_content).expect("Failed to write to temporary file");
295 let config = MutantKrakenConfig::load_config(temp_dir);
296 assert_eq!(config.general.timeout, None);
298 assert_eq!(
299 config.general.operators,
300 vec![
301 ArithmeticReplacementOperator,
302 UnaryRemovalOperator,
303 LogicalReplacementOperator,
304 RelationalReplacementOperator,
305 AssignmentReplacementOperator,
306 UnaryReplacementOperator,
307 NotNullAssertionOperator,
308 ElvisRemoveOperator,
309 ElvisLiteralChangeOperator,
310 LiteralChangeOperator,
311 ExceptionChangeOperator,
312 WhenRemoveBranchOperator,
313 RemoveLabelOperator,
314 FunctionalBinaryReplacementOperator,
315 FunctionalReplacementOperator,
316 ]
317 );
318 }
319
320 #[test]
321 fn test_load_config_from_missing_file() {
322 let temp_dir = temp_dir().join("missing_config");
324 fs::create_dir_all(&temp_dir).expect("Failed to create temporary directory");
326 let config = MutantKrakenConfig::load_config(temp_dir);
328 assert_eq!(config.general.timeout, None);
330 assert_eq!(
331 config.general.operators,
332 vec![
333 ArithmeticReplacementOperator,
334 UnaryRemovalOperator,
335 LogicalReplacementOperator,
336 RelationalReplacementOperator,
337 AssignmentReplacementOperator,
338 UnaryReplacementOperator,
339 NotNullAssertionOperator,
340 ElvisRemoveOperator,
341 ElvisLiteralChangeOperator,
342 LiteralChangeOperator,
343 ExceptionChangeOperator,
344 WhenRemoveBranchOperator,
345 RemoveLabelOperator,
346 FunctionalBinaryReplacementOperator,
347 FunctionalReplacementOperator,
348 ]
349 );
350 }
351}