From 2656af2544cd1773e5b7d57e4306a8cec15ef887 Mon Sep 17 00:00:00 2001 From: sigmaSd Date: Tue, 29 Nov 2022 18:43:54 +0100 Subject: [PATCH] fix(coverage): Error if the emit cache is invalid (#16850) --- cli/cache/emit.rs | 29 ++++----- cli/emit.rs | 6 +- cli/tests/coverage_tests.rs | 62 +++++++++++++++++++ .../coverage/invalid_cache/mod.test.ts | 2 + .../coverage/invalid_cache/mod_after.ts | 6 ++ .../coverage/invalid_cache/mod_before.ts | 15 +++++ cli/tools/coverage/mod.rs | 4 +- 7 files changed, 101 insertions(+), 23 deletions(-) create mode 100644 cli/tests/testdata/coverage/invalid_cache/mod.test.ts create mode 100644 cli/tests/testdata/coverage/invalid_cache/mod_after.ts create mode 100644 cli/tests/testdata/coverage/invalid_cache/mod_before.ts diff --git a/cli/cache/emit.rs b/cli/cache/emit.rs index b3edcbc692..8595627f82 100644 --- a/cli/cache/emit.rs +++ b/cli/cache/emit.rs @@ -44,7 +44,7 @@ impl EmitCache { pub fn get_emit_code( &self, specifier: &ModuleSpecifier, - expected_source_hash: Option, + expected_source_hash: u64, ) -> Option { let meta_filename = self.get_meta_filename(specifier)?; let emit_filename = self.get_emit_filename(specifier)?; @@ -52,10 +52,8 @@ impl EmitCache { // load and verify the meta data file is for this source and CLI version let bytes = self.disk_cache.get(&meta_filename).ok()?; let meta: EmitMetadata = serde_json::from_slice(&bytes).ok()?; - if let Some(expected_source_hash) = expected_source_hash { - if meta.source_hash != expected_source_hash.to_string() { - return None; - } + if meta.source_hash != expected_source_hash.to_string() { + return None; } // load and verify the emit is for the meta data @@ -173,31 +171,26 @@ mod test { let specifier2 = ModuleSpecifier::from_file_path(temp_dir.path().join("file2.ts")) .unwrap(); - assert_eq!(cache.get_emit_code(&specifier1, Some(1)), None); + assert_eq!(cache.get_emit_code(&specifier1, 1), None); let emit_code1 = "text1".to_string(); let emit_code2 = "text2".to_string(); cache.set_emit_code(&specifier1, 10, &emit_code1); cache.set_emit_code(&specifier2, 2, &emit_code2); // providing the incorrect source hash - assert_eq!(cache.get_emit_code(&specifier1, Some(5)), None); + assert_eq!(cache.get_emit_code(&specifier1, 5), None); // providing the correct source hash assert_eq!( - cache.get_emit_code(&specifier1, Some(10)), - Some(emit_code1.clone()), - ); - assert_eq!(cache.get_emit_code(&specifier2, Some(2)), Some(emit_code2)); - // providing no hash - assert_eq!( - cache.get_emit_code(&specifier1, None), + cache.get_emit_code(&specifier1, 10), Some(emit_code1.clone()), ); + assert_eq!(cache.get_emit_code(&specifier2, 2), Some(emit_code2)); // try changing the cli version (should not load previous ones) let cache = EmitCache { disk_cache: disk_cache.clone(), cli_version: "2.0.0".to_string(), }; - assert_eq!(cache.get_emit_code(&specifier1, Some(10)), None); + assert_eq!(cache.get_emit_code(&specifier1, 10), None); cache.set_emit_code(&specifier1, 5, &emit_code1); // recreating the cache should still load the data because the CLI version is the same @@ -205,12 +198,12 @@ mod test { disk_cache, cli_version: "2.0.0".to_string(), }; - assert_eq!(cache.get_emit_code(&specifier1, Some(5)), Some(emit_code1)); + assert_eq!(cache.get_emit_code(&specifier1, 5), Some(emit_code1)); // adding when already exists should not cause issue let emit_code3 = "asdf".to_string(); cache.set_emit_code(&specifier1, 20, &emit_code3); - assert_eq!(cache.get_emit_code(&specifier1, Some(5)), None); - assert_eq!(cache.get_emit_code(&specifier1, Some(20)), Some(emit_code3)); + assert_eq!(cache.get_emit_code(&specifier1, 5), None); + assert_eq!(cache.get_emit_code(&specifier1, 20), Some(emit_code3)); } } diff --git a/cli/emit.rs b/cli/emit.rs index 3bf56fae69..ee591b2a19 100644 --- a/cli/emit.rs +++ b/cli/emit.rs @@ -12,7 +12,7 @@ use std::sync::Arc; /// A hashing function that takes the source code and emit options /// hash then generates a string hash which can be stored to /// determine if the cached emit is valid or not. -fn get_source_hash(source_text: &str, emit_options_hash: u64) -> u64 { +pub fn get_source_hash(source_text: &str, emit_options_hash: u64) -> u64 { FastInsecureHasher::new() .write_str(source_text) .write_u64(emit_options_hash) @@ -30,9 +30,7 @@ pub fn emit_parsed_source( ) -> Result { let source_hash = get_source_hash(source, emit_config_hash); - if let Some(emit_code) = - emit_cache.get_emit_code(specifier, Some(source_hash)) - { + if let Some(emit_code) = emit_cache.get_emit_code(specifier, source_hash) { Ok(emit_code) } else { // this will use a cached version if it exists diff --git a/cli/tests/coverage_tests.rs b/cli/tests/coverage_tests.rs index 1806417ea7..6515fd6eca 100644 --- a/cli/tests/coverage_tests.rs +++ b/cli/tests/coverage_tests.rs @@ -26,6 +26,68 @@ mod coverage { no_snaps_included("no_snaps_included", "ts"); } + #[test] + fn error_if_invalid_cache() { + let deno_dir = TempDir::new(); + let deno_dir_path = deno_dir.path(); + let tempdir = TempDir::new(); + let tempdir = tempdir.path().join("cov"); + + let invalid_cache_path = + util::testdata_path().join("coverage/invalid_cache"); + let mod_before_path = util::testdata_path() + .join(&invalid_cache_path) + .join("mod_before.ts"); + let mod_after_path = util::testdata_path() + .join(&invalid_cache_path) + .join("mod_after.ts"); + let mod_test_path = util::testdata_path() + .join(&invalid_cache_path) + .join("mod.test.ts"); + + let mod_temp_path = deno_dir_path.join("mod.ts"); + let mod_test_temp_path = deno_dir_path.join("mod.test.ts"); + + // Write the inital mod.ts file + std::fs::copy(mod_before_path, &mod_temp_path).unwrap(); + // And the test file + std::fs::copy(mod_test_path, &mod_test_temp_path).unwrap(); + + // Generate coverage + let status = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(deno_dir_path) + .arg("test") + .arg("--quiet") + .arg(format!("--coverage={}", tempdir.to_str().unwrap())) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::inherit()) + .status() + .unwrap(); + + assert!(status.success()); + + // Modify the file between deno test and deno coverage, thus invalidating the cache + std::fs::copy(mod_after_path, mod_temp_path).unwrap(); + + let output = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(deno_dir_path) + .arg("coverage") + .arg(format!("{}/", tempdir.to_str().unwrap())) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .output() + .unwrap(); + + assert!(output.stdout.is_empty()); + + // Expect error + let error = + util::strip_ansi_codes(std::str::from_utf8(&output.stderr).unwrap()) + .to_string(); + assert!(error.contains("error: Missing transpiled source code")); + assert!(error.contains("Before generating coverage report, run `deno test --coverage` to ensure consistent state.")); + } + fn run_coverage_text(test_name: &str, extension: &str) { let deno_dir = TempDir::new(); let tempdir = TempDir::new(); diff --git a/cli/tests/testdata/coverage/invalid_cache/mod.test.ts b/cli/tests/testdata/coverage/invalid_cache/mod.test.ts new file mode 100644 index 0000000000..5815d07a34 --- /dev/null +++ b/cli/tests/testdata/coverage/invalid_cache/mod.test.ts @@ -0,0 +1,2 @@ +import { test } from "./mod.ts"; +Deno.test("test", () => void test()); diff --git a/cli/tests/testdata/coverage/invalid_cache/mod_after.ts b/cli/tests/testdata/coverage/invalid_cache/mod_after.ts new file mode 100644 index 0000000000..294dc0843a --- /dev/null +++ b/cli/tests/testdata/coverage/invalid_cache/mod_after.ts @@ -0,0 +1,6 @@ +export function test() { + return 42; +} +if (import.meta.main) { + test(); +} diff --git a/cli/tests/testdata/coverage/invalid_cache/mod_before.ts b/cli/tests/testdata/coverage/invalid_cache/mod_before.ts new file mode 100644 index 0000000000..ea52ccbcee --- /dev/null +++ b/cli/tests/testdata/coverage/invalid_cache/mod_before.ts @@ -0,0 +1,15 @@ +export function test() { + console.log("1"); + console.log("2"); + console.log("3"); + console.log("4"); + console.log("5"); + console.log("6"); + console.log("7"); + console.log("8"); + console.log("9"); + return 42; +} +if (import.meta.main) { + test(); +} diff --git a/cli/tools/coverage/mod.rs b/cli/tools/coverage/mod.rs index b9bbe14f24..f19cdfa3fa 100644 --- a/cli/tools/coverage/mod.rs +++ b/cli/tools/coverage/mod.rs @@ -3,6 +3,7 @@ use crate::args::CoverageFlags; use crate::args::Flags; use crate::colors; +use crate::emit::get_source_hash; use crate::proc_state::ProcState; use crate::tools::fmt::format_json; use crate::util::fs::collect_files; @@ -677,7 +678,8 @@ pub async fn cover_files( | MediaType::Mts | MediaType::Cts | MediaType::Tsx => { - match ps.emit_cache.get_emit_code(&file.specifier, None) { + let source_hash = get_source_hash(&file.source, ps.emit_options_hash); + match ps.emit_cache.get_emit_code(&file.specifier, source_hash) { Some(code) => code, None => { return Err(anyhow!(