1 use super::*; 2 use crate::config::tests::test_prolog; 3 use std::iter::repeat; 4 use std::process; 5 // load_config! comes from crate::cache(::config::tests); 6 7 // when doing anything with the tests, make sure they are DETERMINISTIC 8 // -- the result shouldn't rely on system time! 9 pub mod system_time_stub; 10 11 #[test] 12 fn test_on_get_create_stats_file() { 13 let (_tempdir, cache_dir, config_path) = test_prolog(); 14 let cache_config = load_config!( 15 config_path, 16 "[cache]\n\ 17 enabled = true\n\ 18 directory = '{cache_dir}'", 19 cache_dir 20 ); 21 assert!(cache_config.enabled()); 22 let worker = Worker::start_new(&cache_config); 23 24 let mod_file = cache_dir.join("some-mod"); 25 worker.on_cache_get_async(mod_file); 26 worker.wait_for_all_events_handled(); 27 assert_eq!(worker.events_dropped(), 0); 28 29 let stats_file = cache_dir.join("some-mod.stats"); 30 let stats = read_stats_file(&stats_file).expect("Failed to read stats file"); 31 assert_eq!(stats.usages, 1); 32 assert_eq!( 33 stats.compression_level, 34 cache_config.baseline_compression_level() 35 ); 36 } 37 38 #[test] 39 fn test_on_get_update_usage_counter() { 40 let (_tempdir, cache_dir, config_path) = test_prolog(); 41 let cache_config = load_config!( 42 config_path, 43 "[cache]\n\ 44 enabled = true\n\ 45 directory = '{cache_dir}'\n\ 46 worker-event-queue-size = '16'", 47 cache_dir 48 ); 49 assert!(cache_config.enabled()); 50 let worker = Worker::start_new(&cache_config); 51 52 let mod_file = cache_dir.join("some-mod"); 53 let stats_file = cache_dir.join("some-mod.stats"); 54 let default_stats = ModuleCacheStatistics::default(&cache_config); 55 assert!(write_stats_file(&stats_file, &default_stats)); 56 57 let mut usages = 0; 58 for times_used in &[4, 7, 2] { 59 for _ in 0..*times_used { 60 worker.on_cache_get_async(mod_file.clone()); 61 usages += 1; 62 } 63 64 worker.wait_for_all_events_handled(); 65 assert_eq!(worker.events_dropped(), 0); 66 67 let stats = read_stats_file(&stats_file).expect("Failed to read stats file"); 68 assert_eq!(stats.usages, usages); 69 } 70 } 71 72 #[test] 73 fn test_on_get_recompress_no_mod_file() { 74 let (_tempdir, cache_dir, config_path) = test_prolog(); 75 let cache_config = load_config!( 76 config_path, 77 "[cache]\n\ 78 enabled = true\n\ 79 directory = '{cache_dir}'\n\ 80 worker-event-queue-size = '16'\n\ 81 baseline-compression-level = 3\n\ 82 optimized-compression-level = 7\n\ 83 optimized-compression-usage-counter-threshold = '256'", 84 cache_dir 85 ); 86 assert!(cache_config.enabled()); 87 let worker = Worker::start_new(&cache_config); 88 89 let mod_file = cache_dir.join("some-mod"); 90 let stats_file = cache_dir.join("some-mod.stats"); 91 let mut start_stats = ModuleCacheStatistics::default(&cache_config); 92 start_stats.usages = 250; 93 assert!(write_stats_file(&stats_file, &start_stats)); 94 95 let mut usages = start_stats.usages; 96 for times_used in &[4, 7, 2] { 97 for _ in 0..*times_used { 98 worker.on_cache_get_async(mod_file.clone()); 99 usages += 1; 100 } 101 102 worker.wait_for_all_events_handled(); 103 assert_eq!(worker.events_dropped(), 0); 104 105 let stats = read_stats_file(&stats_file).expect("Failed to read stats file"); 106 assert_eq!(stats.usages, usages); 107 assert_eq!( 108 stats.compression_level, 109 cache_config.baseline_compression_level() 110 ); 111 } 112 } 113 114 #[test] 115 fn test_on_get_recompress_with_mod_file() { 116 let (_tempdir, cache_dir, config_path) = test_prolog(); 117 let cache_config = load_config!( 118 config_path, 119 "[cache]\n\ 120 enabled = true\n\ 121 directory = '{cache_dir}'\n\ 122 worker-event-queue-size = '16'\n\ 123 baseline-compression-level = 3\n\ 124 optimized-compression-level = 7\n\ 125 optimized-compression-usage-counter-threshold = '256'", 126 cache_dir 127 ); 128 assert!(cache_config.enabled()); 129 let worker = Worker::start_new(&cache_config); 130 131 let mod_file = cache_dir.join("some-mod"); 132 let mod_data = "some test data to be compressed"; 133 let data = zstd::encode_all( 134 mod_data.as_bytes(), 135 cache_config.baseline_compression_level(), 136 ) 137 .expect("Failed to compress sample mod file"); 138 fs::write(&mod_file, &data).expect("Failed to write sample mod file"); 139 140 let stats_file = cache_dir.join("some-mod.stats"); 141 let mut start_stats = ModuleCacheStatistics::default(&cache_config); 142 start_stats.usages = 250; 143 assert!(write_stats_file(&stats_file, &start_stats)); 144 145 // scenarios: 146 // 1. Shouldn't be recompressed 147 // 2. Should be recompressed 148 // 3. After lowering compression level, should be recompressed 149 let scenarios = [(4, false), (7, true), (2, false)]; 150 151 let mut usages = start_stats.usages; 152 assert!(usages < cache_config.optimized_compression_usage_counter_threshold()); 153 let mut tested_higher_opt_compr_lvl = false; 154 for (times_used, lower_compr_lvl) in &scenarios { 155 for _ in 0..*times_used { 156 worker.on_cache_get_async(mod_file.clone()); 157 usages += 1; 158 } 159 160 worker.wait_for_all_events_handled(); 161 assert_eq!(worker.events_dropped(), 0); 162 163 let mut stats = read_stats_file(&stats_file).expect("Failed to read stats file"); 164 assert_eq!(stats.usages, usages); 165 assert_eq!( 166 stats.compression_level, 167 if usages < cache_config.optimized_compression_usage_counter_threshold() { 168 cache_config.baseline_compression_level() 169 } else { 170 cache_config.optimized_compression_level() 171 } 172 ); 173 let compressed_data = fs::read(&mod_file).expect("Failed to read mod file"); 174 let decoded_data = 175 zstd::decode_all(&compressed_data[..]).expect("Failed to decompress mod file"); 176 assert_eq!(decoded_data, mod_data.as_bytes()); 177 178 if *lower_compr_lvl { 179 assert!(usages >= cache_config.optimized_compression_usage_counter_threshold()); 180 tested_higher_opt_compr_lvl = true; 181 stats.compression_level -= 1; 182 assert!(write_stats_file(&stats_file, &stats)); 183 } 184 } 185 assert!(usages >= cache_config.optimized_compression_usage_counter_threshold()); 186 assert!(tested_higher_opt_compr_lvl); 187 } 188 189 #[test] 190 fn test_on_get_recompress_lock() { 191 let (_tempdir, cache_dir, config_path) = test_prolog(); 192 let cache_config = load_config!( 193 config_path, 194 "[cache]\n\ 195 enabled = true\n\ 196 directory = '{cache_dir}'\n\ 197 worker-event-queue-size = '16'\n\ 198 baseline-compression-level = 3\n\ 199 optimized-compression-level = 7\n\ 200 optimized-compression-usage-counter-threshold = '256'\n\ 201 optimizing-compression-task-timeout = '30m'\n\ 202 allowed-clock-drift-for-files-from-future = '1d'", 203 cache_dir 204 ); 205 assert!(cache_config.enabled()); 206 let worker = Worker::start_new(&cache_config); 207 208 let mod_file = cache_dir.join("some-mod"); 209 let mod_data = "some test data to be compressed"; 210 let data = zstd::encode_all( 211 mod_data.as_bytes(), 212 cache_config.baseline_compression_level(), 213 ) 214 .expect("Failed to compress sample mod file"); 215 fs::write(&mod_file, &data).expect("Failed to write sample mod file"); 216 217 let stats_file = cache_dir.join("some-mod.stats"); 218 let mut start_stats = ModuleCacheStatistics::default(&cache_config); 219 start_stats.usages = 255; 220 221 let lock_file = cache_dir.join("some-mod.wip-lock"); 222 223 let scenarios = [ 224 // valid lock 225 (true, "past", Duration::from_secs(30 * 60 - 1)), 226 // valid future lock 227 (true, "future", Duration::from_secs(24 * 60 * 60)), 228 // expired lock 229 (false, "past", Duration::from_secs(30 * 60)), 230 // expired future lock 231 (false, "future", Duration::from_secs(24 * 60 * 60 + 1)), 232 ]; 233 234 for (lock_valid, duration_sign, duration) in &scenarios { 235 assert!(write_stats_file(&stats_file, &start_stats)); // restore usage & compression level 236 create_file_with_mtime(&lock_file, "", duration_sign, &duration); 237 238 worker.on_cache_get_async(mod_file.clone()); 239 worker.wait_for_all_events_handled(); 240 assert_eq!(worker.events_dropped(), 0); 241 242 let stats = read_stats_file(&stats_file).expect("Failed to read stats file"); 243 assert_eq!(stats.usages, start_stats.usages + 1); 244 assert_eq!( 245 stats.compression_level, 246 if *lock_valid { 247 cache_config.baseline_compression_level() 248 } else { 249 cache_config.optimized_compression_level() 250 } 251 ); 252 let compressed_data = fs::read(&mod_file).expect("Failed to read mod file"); 253 let decoded_data = 254 zstd::decode_all(&compressed_data[..]).expect("Failed to decompress mod file"); 255 assert_eq!(decoded_data, mod_data.as_bytes()); 256 } 257 } 258 259 #[test] 260 fn test_on_update_fresh_stats_file() { 261 let (_tempdir, cache_dir, config_path) = test_prolog(); 262 let cache_config = load_config!( 263 config_path, 264 "[cache]\n\ 265 enabled = true\n\ 266 directory = '{cache_dir}'\n\ 267 worker-event-queue-size = '16'\n\ 268 baseline-compression-level = 3\n\ 269 optimized-compression-level = 7\n\ 270 cleanup-interval = '1h'", 271 cache_dir 272 ); 273 assert!(cache_config.enabled()); 274 let worker = Worker::start_new(&cache_config); 275 276 let mod_file = cache_dir.join("some-mod"); 277 let stats_file = cache_dir.join("some-mod.stats"); 278 let cleanup_certificate = cache_dir.join(".cleanup.wip-done"); 279 create_file_with_mtime(&cleanup_certificate, "", "future", &Duration::from_secs(0)); 280 // the below created by the worker if it cleans up 281 let worker_lock_file = cache_dir.join(format!(".cleanup.wip-{}", process::id())); 282 283 // scenarios: 284 // 1. Create new stats file 285 // 2. Overwrite existing file 286 for update_file in &[true, false] { 287 worker.on_cache_update_async(mod_file.clone()); 288 worker.wait_for_all_events_handled(); 289 assert_eq!(worker.events_dropped(), 0); 290 291 let mut stats = read_stats_file(&stats_file).expect("Failed to read stats file"); 292 assert_eq!(stats.usages, 1); 293 assert_eq!( 294 stats.compression_level, 295 cache_config.baseline_compression_level() 296 ); 297 298 if *update_file { 299 stats.usages += 42; 300 stats.compression_level += 1; 301 assert!(write_stats_file(&stats_file, &stats)); 302 } 303 304 assert!(!worker_lock_file.exists()); 305 } 306 } 307 308 #[test] 309 fn test_on_update_cleanup_limits_trash_locks() { 310 let (_tempdir, cache_dir, config_path) = test_prolog(); 311 let cache_config = load_config!( 312 config_path, 313 "[cache]\n\ 314 enabled = true\n\ 315 directory = '{cache_dir}'\n\ 316 worker-event-queue-size = '16'\n\ 317 cleanup-interval = '30m'\n\ 318 optimizing-compression-task-timeout = '30m'\n\ 319 allowed-clock-drift-for-files-from-future = '1d'\n\ 320 file-count-soft-limit = '5'\n\ 321 files-total-size-soft-limit = '30K'\n\ 322 file-count-limit-percent-if-deleting = '70%'\n\ 323 files-total-size-limit-percent-if-deleting = '70%' 324 ", 325 cache_dir 326 ); 327 assert!(cache_config.enabled()); 328 let worker = Worker::start_new(&cache_config); 329 let content_1k = "a".repeat(1_000); 330 let content_10k = "a".repeat(10_000); 331 332 let mods_files_dir = cache_dir.join("target-triple").join("compiler-version"); 333 let mod_with_stats = mods_files_dir.join("mod-with-stats"); 334 let trash_dirs = [ 335 mods_files_dir.join("trash"), 336 mods_files_dir.join("trash").join("trash"), 337 ]; 338 let trash_files = [ 339 cache_dir.join("trash-file"), 340 cache_dir.join("trash-file.wip-lock"), 341 cache_dir.join("target-triple").join("trash.txt"), 342 cache_dir.join("target-triple").join("trash.txt.wip-lock"), 343 mods_files_dir.join("trash.ogg"), 344 mods_files_dir.join("trash").join("trash.doc"), 345 mods_files_dir.join("trash").join("trash.doc.wip-lock"), 346 mods_files_dir.join("trash").join("trash").join("trash.xls"), 347 mods_files_dir 348 .join("trash") 349 .join("trash") 350 .join("trash.xls.wip-lock"), 351 ]; 352 let mod_locks = [ 353 // valid lock 354 ( 355 mods_files_dir.join("mod0.wip-lock"), 356 true, 357 "past", 358 Duration::from_secs(30 * 60 - 1), 359 ), 360 // valid future lock 361 ( 362 mods_files_dir.join("mod1.wip-lock"), 363 true, 364 "future", 365 Duration::from_secs(24 * 60 * 60), 366 ), 367 // expired lock 368 ( 369 mods_files_dir.join("mod2.wip-lock"), 370 false, 371 "past", 372 Duration::from_secs(30 * 60), 373 ), 374 // expired future lock 375 ( 376 mods_files_dir.join("mod3.wip-lock"), 377 false, 378 "future", 379 Duration::from_secs(24 * 60 * 60 + 1), 380 ), 381 ]; 382 // the below created by the worker if it cleans up 383 let worker_lock_file = cache_dir.join(format!(".cleanup.wip-{}", process::id())); 384 385 let scenarios = [ 386 // Close to limits, but not reached, only trash deleted 387 (2, 2, 4), 388 // File count limit exceeded 389 (1, 10, 3), 390 // Total size limit exceeded 391 (4, 0, 2), 392 // Both limits exceeded 393 (3, 5, 3), 394 ]; 395 396 for (files_10k, files_1k, remaining_files) in &scenarios { 397 let mut secs_ago = 100; 398 399 for d in &trash_dirs { 400 fs::create_dir_all(d).expect("Failed to create directories"); 401 } 402 for f in &trash_files { 403 create_file_with_mtime(f, "", "past", &Duration::from_secs(0)); 404 } 405 for (f, _, sign, duration) in &mod_locks { 406 create_file_with_mtime(f, "", sign, &duration); 407 } 408 409 let mut mods_paths = vec![]; 410 for content in repeat(&content_10k) 411 .take(*files_10k) 412 .chain(repeat(&content_1k).take(*files_1k)) 413 { 414 mods_paths.push(mods_files_dir.join(format!("test-mod-{}", mods_paths.len()))); 415 create_file_with_mtime( 416 mods_paths.last().unwrap(), 417 content, 418 "past", 419 &Duration::from_secs(secs_ago), 420 ); 421 assert!(secs_ago > 0); 422 secs_ago -= 1; 423 } 424 425 // creating .stats file updates mtime what affects test results 426 // so we use a separate nonexistent module here (orphaned .stats will be removed anyway) 427 worker.on_cache_update_async(mod_with_stats.clone()); 428 worker.wait_for_all_events_handled(); 429 assert_eq!(worker.events_dropped(), 0); 430 431 for ent in trash_dirs.iter().chain(trash_files.iter()) { 432 assert!(!ent.exists()); 433 } 434 for (f, valid, ..) in &mod_locks { 435 assert_eq!(f.exists(), *valid); 436 } 437 for (idx, path) in mods_paths.iter().enumerate() { 438 let should_exist = idx >= mods_paths.len() - *remaining_files; 439 assert_eq!(path.exists(), should_exist); 440 if should_exist { 441 // cleanup before next iteration 442 fs::remove_file(path).expect("Failed to remove a file"); 443 } 444 } 445 fs::remove_file(&worker_lock_file).expect("Failed to remove lock file"); 446 } 447 } 448 449 #[test] 450 fn test_on_update_cleanup_lru_policy() { 451 let (_tempdir, cache_dir, config_path) = test_prolog(); 452 let cache_config = load_config!( 453 config_path, 454 "[cache]\n\ 455 enabled = true\n\ 456 directory = '{cache_dir}'\n\ 457 worker-event-queue-size = '16'\n\ 458 file-count-soft-limit = '5'\n\ 459 files-total-size-soft-limit = '30K'\n\ 460 file-count-limit-percent-if-deleting = '80%'\n\ 461 files-total-size-limit-percent-if-deleting = '70%'", 462 cache_dir 463 ); 464 assert!(cache_config.enabled()); 465 let worker = Worker::start_new(&cache_config); 466 let content_1k = "a".repeat(1_000); 467 let content_5k = "a".repeat(5_000); 468 let content_10k = "a".repeat(10_000); 469 470 let mods_files_dir = cache_dir.join("target-triple").join("compiler-version"); 471 fs::create_dir_all(&mods_files_dir).expect("Failed to create directories"); 472 let nonexistent_mod_file = cache_dir.join("nonexistent-mod"); 473 let orphaned_stats_file = cache_dir.join("orphaned-mod.stats"); 474 let worker_lock_file = cache_dir.join(format!(".cleanup.wip-{}", process::id())); 475 476 // content, how long ago created, how long ago stats created (if created), should be alive 477 let scenarios = [ 478 &[ 479 (&content_10k, 29, None, false), 480 (&content_10k, 28, None, false), 481 (&content_10k, 27, None, false), 482 (&content_1k, 26, None, true), 483 (&content_10k, 25, None, true), 484 (&content_1k, 24, None, true), 485 ], 486 &[ 487 (&content_10k, 29, None, false), 488 (&content_10k, 28, None, false), 489 (&content_10k, 27, None, true), 490 (&content_1k, 26, None, true), 491 (&content_5k, 25, None, true), 492 (&content_1k, 24, None, true), 493 ], 494 &[ 495 (&content_10k, 29, Some(19), true), 496 (&content_10k, 28, None, false), 497 (&content_10k, 27, None, false), 498 (&content_1k, 26, Some(18), true), 499 (&content_5k, 25, None, true), 500 (&content_1k, 24, None, true), 501 ], 502 &[ 503 (&content_10k, 29, Some(19), true), 504 (&content_10k, 28, Some(18), true), 505 (&content_10k, 27, None, false), 506 (&content_1k, 26, Some(17), true), 507 (&content_5k, 25, None, false), 508 (&content_1k, 24, None, false), 509 ], 510 &[ 511 (&content_10k, 29, Some(19), true), 512 (&content_10k, 28, None, false), 513 (&content_1k, 27, None, false), 514 (&content_5k, 26, Some(18), true), 515 (&content_1k, 25, None, false), 516 (&content_10k, 24, None, false), 517 ], 518 ]; 519 520 for mods in &scenarios { 521 let filenames = (0..mods.len()) 522 .map(|i| { 523 ( 524 mods_files_dir.join(format!("mod-{i}")), 525 mods_files_dir.join(format!("mod-{i}.stats")), 526 ) 527 }) 528 .collect::<Vec<_>>(); 529 530 for ((content, mod_secs_ago, create_stats, _), (mod_filename, stats_filename)) in 531 mods.iter().zip(filenames.iter()) 532 { 533 create_file_with_mtime( 534 mod_filename, 535 content, 536 "past", 537 &Duration::from_secs(*mod_secs_ago), 538 ); 539 if let Some(stats_secs_ago) = create_stats { 540 create_file_with_mtime( 541 stats_filename, 542 "cleanup doesn't care", 543 "past", 544 &Duration::from_secs(*stats_secs_ago), 545 ); 546 } 547 } 548 create_file_with_mtime( 549 &orphaned_stats_file, 550 "cleanup doesn't care", 551 "past", 552 &Duration::from_secs(0), 553 ); 554 555 worker.on_cache_update_async(nonexistent_mod_file.clone()); 556 worker.wait_for_all_events_handled(); 557 assert_eq!(worker.events_dropped(), 0); 558 559 assert!(!orphaned_stats_file.exists()); 560 for ((_, _, create_stats, alive), (mod_filename, stats_filename)) in 561 mods.iter().zip(filenames.iter()) 562 { 563 assert_eq!(mod_filename.exists(), *alive); 564 assert_eq!(stats_filename.exists(), *alive && create_stats.is_some()); 565 566 // cleanup for next iteration 567 if *alive { 568 fs::remove_file(&mod_filename).expect("Failed to remove a file"); 569 if create_stats.is_some() { 570 fs::remove_file(&stats_filename).expect("Failed to remove a file"); 571 } 572 } 573 } 574 575 fs::remove_file(&worker_lock_file).expect("Failed to remove lock file"); 576 } 577 } 578 579 // clock drift should be applied to mod cache & stats, too 580 // however, postpone deleting files to as late as possible 581 #[test] 582 fn test_on_update_cleanup_future_files() { 583 let (_tempdir, cache_dir, config_path) = test_prolog(); 584 let cache_config = load_config!( 585 config_path, 586 "[cache]\n\ 587 enabled = true\n\ 588 directory = '{cache_dir}'\n\ 589 worker-event-queue-size = '16'\n\ 590 allowed-clock-drift-for-files-from-future = '1d'\n\ 591 file-count-soft-limit = '3'\n\ 592 files-total-size-soft-limit = '1M'\n\ 593 file-count-limit-percent-if-deleting = '70%'\n\ 594 files-total-size-limit-percent-if-deleting = '70%'", 595 cache_dir 596 ); 597 assert!(cache_config.enabled()); 598 let worker = Worker::start_new(&cache_config); 599 let content_1k = "a".repeat(1_000); 600 601 let mods_files_dir = cache_dir.join("target-triple").join("compiler-version"); 602 fs::create_dir_all(&mods_files_dir).expect("Failed to create directories"); 603 let nonexistent_mod_file = cache_dir.join("nonexistent-mod"); 604 // the below created by the worker if it cleans up 605 let worker_lock_file = cache_dir.join(format!(".cleanup.wip-{}", process::id())); 606 607 let scenarios: [&[_]; 5] = [ 608 // NOT cleaning up, everything is ok 609 &[ 610 (Duration::from_secs(0), None, true), 611 (Duration::from_secs(24 * 60 * 60), None, true), 612 ], 613 // NOT cleaning up, everything is ok 614 &[ 615 (Duration::from_secs(0), None, true), 616 (Duration::from_secs(24 * 60 * 60 + 1), None, true), 617 ], 618 // cleaning up, removing files from oldest 619 &[ 620 (Duration::from_secs(0), None, false), 621 (Duration::from_secs(24 * 60 * 60), None, true), 622 (Duration::from_secs(1), None, false), 623 (Duration::from_secs(2), None, true), 624 ], 625 // cleaning up, removing files from oldest; deleting file from far future 626 &[ 627 (Duration::from_secs(0), None, false), 628 (Duration::from_secs(1), None, true), 629 (Duration::from_secs(24 * 60 * 60 + 1), None, false), 630 (Duration::from_secs(2), None, true), 631 ], 632 // cleaning up, removing files from oldest; file from far future should have .stats from +-now => it's a legitimate file 633 &[ 634 (Duration::from_secs(0), None, false), 635 (Duration::from_secs(1), None, false), 636 ( 637 Duration::from_secs(24 * 60 * 60 + 1), 638 Some(Duration::from_secs(3)), 639 true, 640 ), 641 (Duration::from_secs(2), None, true), 642 ], 643 ]; 644 645 for mods in &scenarios { 646 let filenames = (0..mods.len()) 647 .map(|i| { 648 ( 649 mods_files_dir.join(format!("mod-{i}")), 650 mods_files_dir.join(format!("mod-{i}.stats")), 651 ) 652 }) 653 .collect::<Vec<_>>(); 654 655 for ((duration, opt_stats_duration, _), (mod_filename, stats_filename)) in 656 mods.iter().zip(filenames.iter()) 657 { 658 create_file_with_mtime(mod_filename, &content_1k, "future", duration); 659 if let Some(stats_duration) = opt_stats_duration { 660 create_file_with_mtime(stats_filename, "", "future", stats_duration); 661 } 662 } 663 664 worker.on_cache_update_async(nonexistent_mod_file.clone()); 665 worker.wait_for_all_events_handled(); 666 assert_eq!(worker.events_dropped(), 0); 667 668 for ((_, opt_stats_duration, alive), (mod_filename, stats_filename)) in 669 mods.iter().zip(filenames.iter()) 670 { 671 assert_eq!(mod_filename.exists(), *alive); 672 assert_eq!( 673 stats_filename.exists(), 674 *alive && opt_stats_duration.is_some() 675 ); 676 if *alive { 677 fs::remove_file(mod_filename).expect("Failed to remove a file"); 678 if opt_stats_duration.is_some() { 679 fs::remove_file(stats_filename).expect("Failed to remove a file"); 680 } 681 } 682 } 683 684 fs::remove_file(&worker_lock_file).expect("Failed to remove lock file"); 685 } 686 } 687 688 // this tests if worker triggered cleanup or not when some cleanup lock/certificate was out there 689 #[test] 690 fn test_on_update_cleanup_self_lock() { 691 let (_tempdir, cache_dir, config_path) = test_prolog(); 692 let cache_config = load_config!( 693 config_path, 694 "[cache]\n\ 695 enabled = true\n\ 696 directory = '{cache_dir}'\n\ 697 worker-event-queue-size = '16'\n\ 698 cleanup-interval = '30m'\n\ 699 allowed-clock-drift-for-files-from-future = '1d'", 700 cache_dir 701 ); 702 assert!(cache_config.enabled()); 703 let worker = Worker::start_new(&cache_config); 704 705 let mod_file = cache_dir.join("some-mod"); 706 let trash_file = cache_dir.join("trash-file.txt"); 707 708 let lock_file = cache_dir.join(".cleanup.wip-lock"); 709 // the below created by the worker if it cleans up 710 let worker_lock_file = cache_dir.join(format!(".cleanup.wip-{}", process::id())); 711 712 let scenarios = [ 713 // valid lock 714 (true, "past", Duration::from_secs(30 * 60 - 1)), 715 // valid future lock 716 (true, "future", Duration::from_secs(24 * 60 * 60)), 717 // expired lock 718 (false, "past", Duration::from_secs(30 * 60)), 719 // expired future lock 720 (false, "future", Duration::from_secs(24 * 60 * 60 + 1)), 721 ]; 722 723 for (lock_valid, duration_sign, duration) in &scenarios { 724 create_file_with_mtime( 725 &trash_file, 726 "with trash content", 727 "future", 728 &Duration::from_secs(0), 729 ); 730 create_file_with_mtime(&lock_file, "", duration_sign, &duration); 731 732 worker.on_cache_update_async(mod_file.clone()); 733 worker.wait_for_all_events_handled(); 734 assert_eq!(worker.events_dropped(), 0); 735 736 assert_eq!(trash_file.exists(), *lock_valid); 737 assert_eq!(lock_file.exists(), *lock_valid); 738 if *lock_valid { 739 assert!(!worker_lock_file.exists()); 740 } else { 741 fs::remove_file(&worker_lock_file).expect("Failed to remove lock file"); 742 } 743 } 744 } 745 746 fn create_file_with_mtime(filename: &Path, contents: &str, offset_sign: &str, offset: &Duration) { 747 fs::write(filename, contents).expect("Failed to create a file"); 748 let mtime = match offset_sign { 749 "past" => system_time_stub::NOW 750 .checked_sub(*offset) 751 .expect("Failed to calculate new mtime"), 752 "future" => system_time_stub::NOW 753 .checked_add(*offset) 754 .expect("Failed to calculate new mtime"), 755 _ => unreachable!(), 756 }; 757 filetime::set_file_mtime(filename, mtime.into()).expect("Failed to set mtime"); 758 } 759