Skip to content

Commit 5f3e392

Browse files
authored
Merge pull request #376 from ryanfowler/add-precompress-argument-tests
Add argument parsing and compressor tests
2 parents 146dd56 + df58341 commit 5f3e392

File tree

2 files changed

+189
-1
lines changed

2 files changed

+189
-1
lines changed

src/main.rs

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,8 @@ fn format_duration(dur: Duration) -> String {
224224
mod tests {
225225
use clap::Parser;
226226

227-
use super::{Args, split_csv};
227+
use super::{Args, calc_savings, parse_compression, split_csv};
228+
use crate::precompress::Algorithm;
228229

229230
#[test]
230231
fn args_respect_ignore_by_default() {
@@ -244,4 +245,73 @@ mod tests {
244245
let values = split_csv(vec![String::from("a,b"), String::from("c")]).collect::<Vec<_>>();
245246
assert_eq!(values, vec!["a", "b", "c"]);
246247
}
248+
249+
#[test]
250+
fn args_accept_repeated_and_comma_separated_filters() {
251+
let args = Args::parse_from([
252+
"precompress",
253+
"--extensions",
254+
"js,css",
255+
"--extensions",
256+
"html",
257+
"--exclude",
258+
"dist/**,build/**",
259+
"--exclude",
260+
"*.map",
261+
".",
262+
]);
263+
264+
assert_eq!(
265+
split_csv(args.extensions.expect("extensions should be parsed")).collect::<Vec<_>>(),
266+
vec!["js", "css", "html"]
267+
);
268+
assert_eq!(
269+
split_csv(args.exclude.expect("exclude should be parsed")).collect::<Vec<_>>(),
270+
vec!["dist/**", "build/**", "*.map"]
271+
);
272+
}
273+
274+
#[test]
275+
fn parse_compression_supports_aliases_and_quality_overrides() {
276+
let (algorithms, quality) = parse_compression(Some(vec![
277+
String::from("br:11,gzip:5"),
278+
String::from("zst:-3"),
279+
]));
280+
281+
assert!(algorithms.brotli);
282+
assert!(!algorithms.deflate);
283+
assert!(algorithms.gzip);
284+
assert!(algorithms.zstd);
285+
assert_eq!(quality.brotli, 11);
286+
assert_eq!(quality.gzip, 5);
287+
assert_eq!(quality.zstd, -3);
288+
}
289+
290+
#[test]
291+
fn parse_compression_defaults_match_enabled_algorithms() {
292+
let (algorithms, quality) = parse_compression(None);
293+
let enabled = algorithms
294+
.iter()
295+
.map(|alg| alg.to_string())
296+
.collect::<Vec<_>>();
297+
298+
assert_eq!(
299+
enabled,
300+
vec![
301+
Algorithm::Brotli.to_string(),
302+
Algorithm::Gzip.to_string(),
303+
Algorithm::Zstd.to_string(),
304+
]
305+
);
306+
assert_eq!(quality.brotli, 10);
307+
assert_eq!(quality.gzip, 7);
308+
assert_eq!(quality.zstd, 19);
309+
}
310+
311+
#[test]
312+
fn calc_savings_handles_zero_positive_and_negative_values() {
313+
assert_eq!(calc_savings(0, 0), 0);
314+
assert_eq!(calc_savings(50, 50), 50);
315+
assert_eq!(calc_savings(-50, 100), 0);
316+
}
247317
}

src/precompress.rs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,7 @@ static EXTENSIONS: phf::Set<&'static str> = phf::phf_set! {
440440
#[cfg(test)]
441441
mod tests {
442442
use std::{
443+
collections::HashSet,
443444
fs,
444445
io::{self, Write},
445446
path::{Path, PathBuf},
@@ -555,6 +556,123 @@ mod tests {
555556
Ok(())
556557
}
557558

559+
#[test]
560+
fn compressor_uses_default_and_custom_extension_filters() {
561+
let default = Compressor::new(1, 1, Quality::default(), Algorithms::default(), None, false);
562+
let custom = Compressor::new(
563+
1,
564+
1,
565+
Quality::default(),
566+
Algorithms::default(),
567+
Some(HashSet::from([String::from("bin")])),
568+
false,
569+
);
570+
571+
assert!(default.should_compress(Path::new("asset.js")));
572+
assert!(!default.should_compress(Path::new("asset.bin")));
573+
assert!(!default.should_compress(Path::new("LICENSE")));
574+
assert!(custom.should_compress(Path::new("asset.bin")));
575+
assert!(!custom.should_compress(Path::new("asset.js")));
576+
}
577+
578+
#[test]
579+
fn compressor_skips_small_and_filtered_out_files() -> Result<()> {
580+
let root = test_dir("skip-files");
581+
fs::write(root.join("small.js"), "tiny")?;
582+
fs::write(root.join("note.txt"), "ignored extension")?;
583+
584+
let compressor = Compressor::new(
585+
1,
586+
32,
587+
Quality::default(),
588+
Algorithms {
589+
brotli: false,
590+
deflate: false,
591+
gzip: true,
592+
zstd: false,
593+
},
594+
Some(HashSet::from([String::from("js")])),
595+
false,
596+
);
597+
compressor.precompress(&root, &WalkOptions::default())?;
598+
let stats = compressor.finish();
599+
600+
assert_eq!(stats.num_source_files, 0);
601+
assert_eq!(stats.num_errors, 0);
602+
assert!(!root.join("small.js.gz").exists());
603+
assert!(!root.join("note.txt.gz").exists());
604+
605+
fs::remove_dir_all(root)?;
606+
Ok(())
607+
}
608+
609+
#[test]
610+
fn compressor_overwrites_existing_outputs() -> Result<()> {
611+
let root = test_dir("overwrite-output");
612+
let src_path = root.join("asset.js");
613+
fs::write(&src_path, "const payload = 'hello world';\n".repeat(256))?;
614+
let dst_path = root.join("asset.js.gz");
615+
fs::write(&dst_path, b"stale artifact")?;
616+
617+
let original = fs::read(&dst_path)?;
618+
let compressor = Compressor::new(
619+
1,
620+
1,
621+
Quality::default(),
622+
Algorithms {
623+
brotli: false,
624+
deflate: false,
625+
gzip: true,
626+
zstd: false,
627+
},
628+
None,
629+
false,
630+
);
631+
compressor.precompress(&root, &WalkOptions::default())?;
632+
let stats = compressor.finish();
633+
634+
assert_eq!(stats.num_source_files, 1);
635+
assert_eq!(stats.num_errors, 0);
636+
assert_ne!(fs::read(&dst_path)?, original);
637+
638+
fs::remove_dir_all(root)?;
639+
Ok(())
640+
}
641+
642+
#[test]
643+
fn compressor_cleans_up_temp_output_after_failed_replace() -> Result<()> {
644+
let root = test_dir("cleanup-failed-replace");
645+
let src_path = root.join("asset.js");
646+
fs::write(&src_path, "const payload = 'hello world';\n".repeat(256))?;
647+
let dst_path = root.join("asset.js.gz");
648+
let tmp_path = tmp_output_path(&dst_path);
649+
fs::create_dir(&dst_path)?;
650+
651+
let compressor = Compressor::new(
652+
1,
653+
1,
654+
Quality::default(),
655+
Algorithms {
656+
brotli: false,
657+
deflate: false,
658+
gzip: true,
659+
zstd: false,
660+
},
661+
None,
662+
false,
663+
);
664+
compressor.precompress(&root, &WalkOptions::default())?;
665+
let stats = compressor.finish();
666+
667+
assert_eq!(stats.num_source_files, 0);
668+
assert_eq!(stats.num_errors, 1);
669+
assert!(dst_path.is_dir());
670+
assert!(!tmp_path.exists());
671+
672+
fs::remove_dir_all(root)?;
673+
Ok(())
674+
}
675+
558676
fn walk_paths(root: &Path, options: &WalkOptions) -> Result<Vec<String>> {
559677
let mut paths = build_walk(root, options)?
560678
.filter_map(|entry| entry.ok())

0 commit comments

Comments
 (0)