#![allow(clippy::unwrap_used, clippy::expect_used)]
mod support;
use std::fs;
use std::path::Path;
use support::{fixture, run, skif};
fn project() -> tempfile::TempDir {
let dir = fixture("js");
let app = dir.path().join("packages").join("app");
fs::write(
app.join("index.js"),
"import 'left-pad';\nimport { used } from './util.ts';\nimport './style.css';\nused();\n",
)
.unwrap();
fs::write(
app.join("util.ts"),
"export const used = () => 1;\nexport const unused = 2;\n",
)
.unwrap();
fs::write(app.join("style.css"), "@import './base.css';\n").unwrap();
fs::write(app.join("base.css"), "a { color: red; }\n").unwrap();
fs::write(app.join("orphan.ts"), "export const orphan = 3;\n").unwrap();
dir
}
fn report(dir: &Path, flags: &[&str]) -> (String, i32) {
let result = run(skif().current_dir(dir).args(["--format", "json"]).args(flags));
assert!(matches!(result.code, 0 | 1), "{}", result.stderr);
assert!(!result.stderr.contains("warning"), "{}", result.stderr);
(result.stdout, result.code)
}
#[test]
fn no_cache_a_cold_cache_and_a_warm_cache_print_the_same_report() {
let dir = project();
let store = tempfile::tempdir().unwrap();
let location = store.path().join("nested").join("cache");
let flags = ["--cache", "--cache-location", location.to_str().unwrap()];
let file = location.join("modules.bin");
let uncached = report(dir.path(), &[]);
assert!(
uncached.0.contains("unused-exports") || uncached.0.contains("unused-files"),
"{}",
uncached.0
);
let cold = report(dir.path(), &flags);
let written = fs::read(&file).unwrap();
assert!(written.starts_with(b"SKIFMOD\0"));
let warm = report(dir.path(), &flags);
assert_eq!(cold, uncached);
assert_eq!(warm, uncached);
assert_eq!(fs::read(&file).unwrap(), written);
fs::write(&file, b"garbage").unwrap();
assert_eq!(report(dir.path(), &flags), uncached);
assert_eq!(fs::read(&file).unwrap(), written);
fs::write(
dir.path().join("packages").join("app").join("util.ts"),
"export const used = () => 1;\nexport const unused = 2;\nexport const another = 3;\n",
)
.unwrap();
let edited = report(dir.path(), &[]);
assert_ne!(edited, uncached);
assert_eq!(report(dir.path(), &flags), edited);
assert!(fs::read(&file).unwrap() != written, "the edited file was not stored");
assert_eq!(report(dir.path(), &flags), edited);
}
#[test]
fn the_default_store_is_below_node_modules_of_the_root() {
let dir = project();
let uncached = report(dir.path(), &[]);
let cold = report(&dir.path().join("packages").join("app"), &["--cache"]);
let file = dir
.path()
.join("node_modules")
.join(".cache")
.join("skif")
.join("modules.bin");
assert!(fs::read(&file).unwrap().starts_with(b"SKIFMOD\0"));
let warm = report(dir.path(), &["--cache"]);
assert_eq!(cold, uncached);
assert_eq!(warm, uncached);
}
#[test]
fn a_store_that_cannot_be_written_is_a_warning_only() {
let dir = project();
let uncached = report(dir.path(), &[]);
let blocked = dir.path().join("blocked");
fs::write(&blocked, "").unwrap();
let result = run(skif().current_dir(dir.path()).args([
"--format",
"json",
"--cache",
"--cache-location",
blocked.to_str().unwrap(),
]));
assert_eq!((result.stdout, result.code), uncached);
assert!(
result.stderr.contains("warning: cannot write the module cache to"),
"{}",
result.stderr
);
}
#[test]
fn cache_location_requires_cache() {
let dir = project();
let result = run(skif().current_dir(dir.path()).args(["--cache-location", "x"]));
assert_eq!(result.code, 2, "{}", result.stderr);
assert!(result.stderr.contains("--cache"), "{}", result.stderr);
assert!(!dir.path().join("x").exists());
}
fn shared_closure() -> tempfile::TempDir {
let dir = tempfile::tempdir().unwrap();
for (name, offset) in [("a", 1), ("b", 2), ("c", 3)] {
let root = dir.path().join(name);
fs::create_dir_all(&root).unwrap();
fs::write(
root.join("package.json"),
format!(r#"{{"name": "{name}", "version": "1.0.0", "main": "index.ts"}}"#),
)
.unwrap();
fs::write(
root.join("index.ts"),
format!("import {{ shared }} from '../shared/lib';\nexport const {name} = shared + {offset};\n"),
)
.unwrap();
}
fs::create_dir_all(dir.path().join("shared")).unwrap();
fs::write(dir.path().join("shared").join("lib.ts"), "export const shared = 1;\n").unwrap();
dir
}
fn module_cache_counts(stderr: &str) -> (usize, usize) {
stderr
.lines()
.filter(|line| line.contains("js module cache "))
.map(|line| {
let counts: Vec<usize> = line
.rsplit(": ")
.next()
.unwrap()
.split(", ")
.map(|count| count.split_once(' ').unwrap().0.parse().unwrap())
.collect();
let [hits, misses, waits] = counts.as_slice() else {
panic!("not three counts: {line}");
};
(hits + waits, *misses)
})
.fold((0, 0), |(served, parsed), (s, p)| (served + s, parsed + p))
}
fn recursive_run(dir: &Path, flags: &[&str]) -> ((String, i32), (usize, usize)) {
let result = run(skif()
.current_dir(dir)
.args(["--recursive", "--performance", "--format", "json"])
.args(flags));
assert!(matches!(result.code, 0 | 1), "{}", result.stderr);
let counts = module_cache_counts(&result.stderr);
((result.stdout, result.code), counts)
}
#[test]
fn a_recursive_run_parses_a_file_several_roots_import_once() {
let dir = shared_closure();
let (report, counts) = recursive_run(dir.path(), &[]);
assert_eq!(root_count(&report.0), 3, "{}", report.0);
assert_eq!(counts, (2, 1));
let store = tempfile::tempdir().unwrap();
let flags = ["--cache", "--cache-location", store.path().to_str().unwrap()];
let (cold, cold_counts) = recursive_run(dir.path(), &flags);
assert_eq!(cold_counts, (2, 4));
let (warm, warm_counts) = recursive_run(dir.path(), &flags);
assert_eq!(warm_counts, (6, 0));
assert_eq!(cold, report);
assert_eq!(warm, report);
}
fn root_count(report: &str) -> usize {
let report: serde_json::Value = serde_json::from_str(report).unwrap();
report["roots"].as_array().unwrap().len()
}