1 use super::CacheConfig;
2 use std::fs;
3 use std::path::PathBuf;
4 use std::time::Duration;
5 use tempfile::TempDir;
6 
7 // note: config loading during validation creates cache directory to canonicalize its path,
8 //       that's why these function and macro always use custom cache directory
9 // note: tempdir removes directory when being dropped, so we need to return it to the caller,
10 //       so the paths are valid
test_prolog() -> (TempDir, PathBuf, PathBuf)11 pub fn test_prolog() -> (TempDir, PathBuf, PathBuf) {
12     let _ = env_logger::try_init();
13     let temp_dir = tempfile::tempdir().expect("Can't create temporary directory");
14     let cache_dir = temp_dir.path().join("cache-dir");
15     let config_path = temp_dir.path().join("cache-config.toml");
16     (temp_dir, cache_dir, config_path)
17 }
18 
19 macro_rules! load_config {
20     ($config_path:ident, $content_fmt:expr, $cache_dir:ident) => {{
21         let config_path = &$config_path;
22         let content = format!($content_fmt, cache_dir = $cache_dir.display());
23         fs::write(config_path, content).expect("Failed to write test config file");
24         CacheConfig::from_file(Some(config_path)).unwrap()
25     }};
26 }
27 
28 macro_rules! bad_config {
29     ($config_path:ident, $content_fmt:expr, $cache_dir:ident) => {{
30         let config_path = &$config_path;
31         let content = format!($content_fmt, cache_dir = $cache_dir.display());
32         fs::write(config_path, content).expect("Failed to write test config file");
33         assert!(CacheConfig::from_file(Some(config_path)).is_err());
34     }};
35 }
36 
37 #[test]
test_unrecognized_settings()38 fn test_unrecognized_settings() {
39     let (_td, cd, cp) = test_prolog();
40     bad_config!(
41         cp,
42         "unrecognized-setting = 42\n\
43          [cache]\n\
44          directory = '{cache_dir}'",
45         cd
46     );
47 
48     bad_config!(
49         cp,
50         "[cache]\n\
51          directory = '{cache_dir}'\n\
52          unrecognized-setting = 42",
53         cd
54     );
55 }
56 
57 #[test]
test_all_settings()58 fn test_all_settings() {
59     let (_td, cd, cp) = test_prolog();
60     let conf = load_config!(
61         cp,
62         "[cache]\n\
63          directory = '{cache_dir}'\n\
64          worker-event-queue-size = '16'\n\
65          baseline-compression-level = 3\n\
66          optimized-compression-level = 20\n\
67          optimized-compression-usage-counter-threshold = '256'\n\
68          cleanup-interval = '1h'\n\
69          optimizing-compression-task-timeout = '30m'\n\
70          allowed-clock-drift-for-files-from-future = '1d'\n\
71          file-count-soft-limit = '65536'\n\
72          files-total-size-soft-limit = '512Mi'\n\
73          file-count-limit-percent-if-deleting = '70%'\n\
74          files-total-size-limit-percent-if-deleting = '70%'",
75         cd
76     );
77     check_conf(&conf, &cd);
78 
79     let conf = load_config!(
80         cp,
81         // added some white spaces
82         "[cache]\n\
83          directory = '{cache_dir}'\n\
84          worker-event-queue-size  = ' 16\t'\n\
85          baseline-compression-level = 3\n\
86          optimized-compression-level =\t 20\n\
87          optimized-compression-usage-counter-threshold = '256'\n\
88          cleanup-interval = ' 1h'\n\
89          optimizing-compression-task-timeout = '30  m'\n\
90          allowed-clock-drift-for-files-from-future = '1\td'\n\
91          file-count-soft-limit = '\t \t65536\t'\n\
92          files-total-size-soft-limit = '512\t\t Mi '\n\
93          file-count-limit-percent-if-deleting = '70\t%'\n\
94          files-total-size-limit-percent-if-deleting = ' 70 %'",
95         cd
96     );
97     check_conf(&conf, &cd);
98 
99     fn check_conf(conf: &CacheConfig, cd: &PathBuf) {
100         assert_eq!(
101             conf.directory(),
102             Some(&fs::canonicalize(cd).expect("canonicalize failed"))
103         );
104         assert_eq!(conf.worker_event_queue_size(), 0x10);
105         assert_eq!(conf.baseline_compression_level(), 3);
106         assert_eq!(conf.optimized_compression_level(), 20);
107         assert_eq!(conf.optimized_compression_usage_counter_threshold(), 0x100);
108         assert_eq!(conf.cleanup_interval(), Duration::from_secs(60 * 60));
109         assert_eq!(
110             conf.optimizing_compression_task_timeout(),
111             Duration::from_secs(30 * 60)
112         );
113         assert_eq!(
114             conf.allowed_clock_drift_for_files_from_future(),
115             Duration::from_secs(60 * 60 * 24)
116         );
117         assert_eq!(conf.file_count_soft_limit(), 0x10_000);
118         assert_eq!(conf.files_total_size_soft_limit(), 512 * (1u64 << 20));
119         assert_eq!(conf.file_count_limit_percent_if_deleting(), 70);
120         assert_eq!(conf.files_total_size_limit_percent_if_deleting(), 70);
121     }
122 }
123 
124 #[test]
test_compression_level_settings()125 fn test_compression_level_settings() {
126     let (_td, cd, cp) = test_prolog();
127     let conf = load_config!(
128         cp,
129         "[cache]\n\
130          directory = '{cache_dir}'\n\
131          baseline-compression-level = 1\n\
132          optimized-compression-level = 21",
133         cd
134     );
135     assert_eq!(conf.baseline_compression_level(), 1);
136     assert_eq!(conf.optimized_compression_level(), 21);
137 
138     bad_config!(
139         cp,
140         "[cache]\n\
141          directory = '{cache_dir}'\n\
142          baseline-compression-level = -1\n\
143          optimized-compression-level = 21",
144         cd
145     );
146 
147     bad_config!(
148         cp,
149         "[cache]\n\
150          directory = '{cache_dir}'\n\
151          baseline-compression-level = 15\n\
152          optimized-compression-level = 10",
153         cd
154     );
155 }
156 
157 #[test]
test_si_prefix_settings()158 fn test_si_prefix_settings() {
159     let (_td, cd, cp) = test_prolog();
160     let conf = load_config!(
161         cp,
162         "[cache]\n\
163          directory = '{cache_dir}'\n\
164          worker-event-queue-size = '42'\n\
165          optimized-compression-usage-counter-threshold = '4K'\n\
166          file-count-soft-limit = '3M'",
167         cd
168     );
169     assert_eq!(conf.worker_event_queue_size(), 42);
170     assert_eq!(conf.optimized_compression_usage_counter_threshold(), 4_000);
171     assert_eq!(conf.file_count_soft_limit(), 3_000_000);
172 
173     let conf = load_config!(
174         cp,
175         "[cache]\n\
176          directory = '{cache_dir}'\n\
177          worker-event-queue-size = '2K'\n\
178          optimized-compression-usage-counter-threshold = '4444T'\n\
179          file-count-soft-limit = '1P'",
180         cd
181     );
182     assert_eq!(conf.worker_event_queue_size(), 2_000);
183     assert_eq!(
184         conf.optimized_compression_usage_counter_threshold(),
185         4_444_000_000_000_000
186     );
187     assert_eq!(conf.file_count_soft_limit(), 1_000_000_000_000_000);
188 
189     // different errors
190     bad_config!(
191         cp,
192         "[cache]\n\
193          directory = '{cache_dir}'\n\
194          worker-event-queue-size = '2g'",
195         cd
196     );
197 
198     bad_config!(
199         cp,
200         "[cache]\n\
201          directory = '{cache_dir}'\n\
202          file-count-soft-limit = 1",
203         cd
204     );
205 
206     bad_config!(
207         cp,
208         "[cache]\n\
209          directory = '{cache_dir}'\n\
210          file-count-soft-limit = '-31337'",
211         cd
212     );
213 
214     bad_config!(
215         cp,
216         "[cache]\n\
217          directory = '{cache_dir}'\n\
218          file-count-soft-limit = '3.14M'",
219         cd
220     );
221 }
222 
223 #[test]
test_disk_space_settings()224 fn test_disk_space_settings() {
225     let (_td, cd, cp) = test_prolog();
226     let conf = load_config!(
227         cp,
228         "[cache]\n\
229          directory = '{cache_dir}'\n\
230          files-total-size-soft-limit = '76'",
231         cd
232     );
233     assert_eq!(conf.files_total_size_soft_limit(), 76);
234 
235     let conf = load_config!(
236         cp,
237         "[cache]\n\
238          directory = '{cache_dir}'\n\
239          files-total-size-soft-limit = '42 Mi'",
240         cd
241     );
242     assert_eq!(conf.files_total_size_soft_limit(), 42 * (1u64 << 20));
243 
244     let conf = load_config!(
245         cp,
246         "[cache]\n\
247          directory = '{cache_dir}'\n\
248          files-total-size-soft-limit = '2 Gi'",
249         cd
250     );
251     assert_eq!(conf.files_total_size_soft_limit(), 2 * (1u64 << 30));
252 
253     let conf = load_config!(
254         cp,
255         "[cache]\n\
256          directory = '{cache_dir}'\n\
257          files-total-size-soft-limit = '31337 Ti'",
258         cd
259     );
260     assert_eq!(conf.files_total_size_soft_limit(), 31337 * (1u64 << 40));
261 
262     let conf = load_config!(
263         cp,
264         "[cache]\n\
265          directory = '{cache_dir}'\n\
266          files-total-size-soft-limit = '7 Pi'",
267         cd
268     );
269     assert_eq!(conf.files_total_size_soft_limit(), 7 * (1u64 << 50));
270 
271     let conf = load_config!(
272         cp,
273         "[cache]\n\
274          directory = '{cache_dir}'\n\
275          files-total-size-soft-limit = '7M'",
276         cd
277     );
278     assert_eq!(conf.files_total_size_soft_limit(), 7_000_000);
279 
280     // different errors
281     bad_config!(
282         cp,
283         "[cache]\n\
284          directory = '{cache_dir}'\n\
285          files-total-size-soft-limit = '7 mi'",
286         cd
287     );
288 
289     bad_config!(
290         cp,
291         "[cache]\n\
292          directory = '{cache_dir}'\n\
293          files-total-size-soft-limit = 1",
294         cd
295     );
296 
297     bad_config!(
298         cp,
299         "[cache]\n\
300          directory = '{cache_dir}'\n\
301          files-total-size-soft-limit = '-31337'",
302         cd
303     );
304 
305     bad_config!(
306         cp,
307         "[cache]\n\
308          directory = '{cache_dir}'\n\
309          files-total-size-soft-limit = '3.14Ki'",
310         cd
311     );
312 }
313 
314 #[test]
test_duration_settings()315 fn test_duration_settings() {
316     let (_td, cd, cp) = test_prolog();
317     let conf = load_config!(
318         cp,
319         "[cache]\n\
320          directory = '{cache_dir}'\n\
321          cleanup-interval = '100s'\n\
322          optimizing-compression-task-timeout = '3m'\n\
323          allowed-clock-drift-for-files-from-future = '4h'",
324         cd
325     );
326     assert_eq!(conf.cleanup_interval(), Duration::from_secs(100));
327     assert_eq!(
328         conf.optimizing_compression_task_timeout(),
329         Duration::from_secs(3 * 60)
330     );
331     assert_eq!(
332         conf.allowed_clock_drift_for_files_from_future(),
333         Duration::from_secs(4 * 60 * 60)
334     );
335 
336     let conf = load_config!(
337         cp,
338         "[cache]\n\
339          directory = '{cache_dir}'\n\
340          cleanup-interval = '2d'\n\
341          optimizing-compression-task-timeout = '333 m'",
342         cd
343     );
344     assert_eq!(
345         conf.cleanup_interval(),
346         Duration::from_secs(2 * 24 * 60 * 60)
347     );
348     assert_eq!(
349         conf.optimizing_compression_task_timeout(),
350         Duration::from_secs(333 * 60)
351     );
352 
353     // different errors
354     bad_config!(
355         cp,
356         "[cache]\n\
357          directory = '{cache_dir}'\n\
358          optimizing-compression-task-timeout = '333'",
359         cd
360     );
361 
362     bad_config!(
363         cp,
364         "[cache]\n\
365          directory = '{cache_dir}'\n\
366          optimizing-compression-task-timeout = 333",
367         cd
368     );
369 
370     bad_config!(
371         cp,
372         "[cache]\n\
373          directory = '{cache_dir}'\n\
374          optimizing-compression-task-timeout = '10 M'",
375         cd
376     );
377 
378     bad_config!(
379         cp,
380         "[cache]\n\
381          directory = '{cache_dir}'\n\
382          optimizing-compression-task-timeout = '10 min'",
383         cd
384     );
385 
386     bad_config!(
387         cp,
388         "[cache]\n\
389          directory = '{cache_dir}'\n\
390          optimizing-compression-task-timeout = '-10s'",
391         cd
392     );
393 
394     bad_config!(
395         cp,
396         "[cache]\n\
397          directory = '{cache_dir}'\n\
398          optimizing-compression-task-timeout = '1.5m'",
399         cd
400     );
401 }
402 
403 #[test]
test_percent_settings()404 fn test_percent_settings() {
405     let (_td, cd, cp) = test_prolog();
406     let conf = load_config!(
407         cp,
408         "[cache]\n\
409          directory = '{cache_dir}'\n\
410          file-count-limit-percent-if-deleting = '62%'\n\
411          files-total-size-limit-percent-if-deleting = '23 %'",
412         cd
413     );
414     assert_eq!(conf.file_count_limit_percent_if_deleting(), 62);
415     assert_eq!(conf.files_total_size_limit_percent_if_deleting(), 23);
416 
417     // different errors
418     bad_config!(
419         cp,
420         "[cache]\n\
421          directory = '{cache_dir}'\n\
422          files-total-size-limit-percent-if-deleting = '23'",
423         cd
424     );
425 
426     bad_config!(
427         cp,
428         "[cache]\n\
429          directory = '{cache_dir}'\n\
430          files-total-size-limit-percent-if-deleting = '22.5%'",
431         cd
432     );
433 
434     bad_config!(
435         cp,
436         "[cache]\n\
437          directory = '{cache_dir}'\n\
438          files-total-size-limit-percent-if-deleting = '0.5'",
439         cd
440     );
441 
442     bad_config!(
443         cp,
444         "[cache]\n\
445          directory = '{cache_dir}'\n\
446          files-total-size-limit-percent-if-deleting = '-1%'",
447         cd
448     );
449 
450     bad_config!(
451         cp,
452         "[cache]\n\
453          directory = '{cache_dir}'\n\
454          files-total-size-limit-percent-if-deleting = '101%'",
455         cd
456     );
457 }
458 
459 /// Default builder produces a disabled cache configuration with the same defaults.
460 #[test]
test_builder_default()461 fn test_builder_default() {
462     let (_td, _cd, cp) = test_prolog();
463     let config_content = "[cache]\n";
464     fs::write(&cp, config_content).expect("Failed to write test config file");
465     let expected_config = CacheConfig::from_file(Some(&cp)).unwrap();
466 
467     let mut config = CacheConfig::new();
468     config
469         .validate()
470         .expect("Failed to validate default config");
471 
472     assert_eq!(config.directory, expected_config.directory);
473     assert_eq!(
474         config.worker_event_queue_size,
475         expected_config.worker_event_queue_size
476     );
477     assert_eq!(
478         config.baseline_compression_level,
479         expected_config.baseline_compression_level
480     );
481     assert_eq!(
482         config.optimized_compression_level,
483         expected_config.optimized_compression_level
484     );
485     assert_eq!(
486         config.optimized_compression_usage_counter_threshold,
487         expected_config.optimized_compression_usage_counter_threshold
488     );
489     assert_eq!(config.cleanup_interval, expected_config.cleanup_interval);
490     assert_eq!(
491         config.optimizing_compression_task_timeout,
492         expected_config.optimizing_compression_task_timeout
493     );
494     assert_eq!(
495         config.allowed_clock_drift_for_files_from_future,
496         expected_config.allowed_clock_drift_for_files_from_future
497     );
498     assert_eq!(
499         config.file_count_soft_limit,
500         expected_config.file_count_soft_limit
501     );
502     assert_eq!(
503         config.files_total_size_soft_limit,
504         expected_config.files_total_size_soft_limit
505     );
506     assert_eq!(
507         config.file_count_limit_percent_if_deleting,
508         expected_config.file_count_limit_percent_if_deleting
509     );
510     assert_eq!(
511         config.files_total_size_limit_percent_if_deleting,
512         expected_config.files_total_size_limit_percent_if_deleting
513     );
514 }
515 
516 #[test]
test_builder_all_settings()517 fn test_builder_all_settings() {
518     let (_td, cd, _cp) = test_prolog();
519 
520     let mut conf = CacheConfig::new();
521     conf.with_directory(&cd)
522         .with_worker_event_queue_size(0x10)
523         .with_baseline_compression_level(3)
524         .with_optimized_compression_level(20)
525         .with_optimized_compression_usage_counter_threshold(0x100)
526         .with_cleanup_interval(Duration::from_secs(60 * 60))
527         .with_optimizing_compression_task_timeout(Duration::from_secs(30 * 60))
528         .with_allowed_clock_drift_for_files_from_future(Duration::from_secs(60 * 60 * 24))
529         .with_file_count_soft_limit(0x10_000)
530         .with_files_total_size_soft_limit(512 * (1u64 << 20))
531         .with_file_count_limit_percent_if_deleting(70)
532         .with_files_total_size_limit_percent_if_deleting(70);
533     conf.validate().expect("validation failed");
534     check_conf(&conf, &cd);
535 
536     fn check_conf(conf: &CacheConfig, cd: &PathBuf) {
537         assert_eq!(
538             conf.directory(),
539             Some(&fs::canonicalize(cd).expect("canonicalize failed"))
540         );
541         assert_eq!(conf.worker_event_queue_size(), 0x10);
542         assert_eq!(conf.baseline_compression_level(), 3);
543         assert_eq!(conf.optimized_compression_level(), 20);
544         assert_eq!(conf.optimized_compression_usage_counter_threshold(), 0x100);
545         assert_eq!(conf.cleanup_interval(), Duration::from_secs(60 * 60));
546         assert_eq!(
547             conf.optimizing_compression_task_timeout(),
548             Duration::from_secs(30 * 60)
549         );
550         assert_eq!(
551             conf.allowed_clock_drift_for_files_from_future(),
552             Duration::from_secs(60 * 60 * 24)
553         );
554         assert_eq!(conf.file_count_soft_limit(), 0x10_000);
555         assert_eq!(conf.files_total_size_soft_limit(), 512 * (1u64 << 20));
556         assert_eq!(conf.file_count_limit_percent_if_deleting(), 70);
557         assert_eq!(conf.files_total_size_limit_percent_if_deleting(), 70);
558     }
559 }
560