mirror of
https://github.com/denoland/deno.git
synced 2024-12-11 18:17:48 -05:00
b708a13eb0
Stores normalized version constraints in the lockfile, which will improve reproducibility and will fix a bug with duplicate specifiers ending up in the lockfile. Also, gets rid of some duplicate data in the specifiers area of the lockfile.
430 lines
13 KiB
Rust
430 lines
13 KiB
Rust
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
|
|
|
use deno_cache_dir::HttpCache;
|
|
use deno_core::serde_json;
|
|
use deno_core::serde_json::json;
|
|
use deno_core::serde_json::Value;
|
|
use deno_lockfile::Lockfile;
|
|
use deno_lockfile::NewLockfileOptions;
|
|
use deno_semver::jsr::JsrDepPackageReq;
|
|
use deno_semver::package::PackageNv;
|
|
use test_util as util;
|
|
use url::Url;
|
|
use util::assert_contains;
|
|
use util::assert_not_contains;
|
|
use util::TestContextBuilder;
|
|
|
|
#[test]
|
|
fn fast_check_cache() {
|
|
let test_context = TestContextBuilder::for_jsr().use_temp_cwd().build();
|
|
let deno_dir = test_context.deno_dir();
|
|
let temp_dir = test_context.temp_dir();
|
|
let type_check_cache_path = deno_dir.path().join("check_cache_v2");
|
|
|
|
temp_dir.write(
|
|
"main.ts",
|
|
r#"import { add } from "jsr:@denotest/add@1";
|
|
const value: number = add(1, 2);
|
|
console.log(value);"#,
|
|
);
|
|
temp_dir.path().join("deno.json").write_json(&json!({
|
|
"vendor": true
|
|
}));
|
|
|
|
test_context
|
|
.new_command()
|
|
.args("check main.ts")
|
|
.run()
|
|
.skip_output_check();
|
|
|
|
type_check_cache_path.remove_file();
|
|
let check_debug_cmd = test_context
|
|
.new_command()
|
|
.args("check --log-level=debug main.ts");
|
|
let output = check_debug_cmd.run();
|
|
assert_contains!(
|
|
output.combined_output(),
|
|
"Using FastCheck cache for: @denotest/add@1.0.0"
|
|
);
|
|
|
|
// modify the file in the vendor folder
|
|
let vendor_dir = temp_dir.path().join("vendor");
|
|
let pkg_dir = vendor_dir.join("http_127.0.0.1_4250/@denotest/add/1.0.0/");
|
|
pkg_dir
|
|
.join("mod.ts")
|
|
.append("\nexport * from './other.ts';");
|
|
let nested_pkg_file = pkg_dir.join("other.ts");
|
|
nested_pkg_file.write("export function other(): string { return ''; }");
|
|
|
|
// invalidated
|
|
let output = check_debug_cmd.run();
|
|
assert_not_contains!(
|
|
output.combined_output(),
|
|
"Using FastCheck cache for: @denotest/add@1.0.0"
|
|
);
|
|
|
|
// ensure cache works
|
|
let output = check_debug_cmd.run();
|
|
assert_contains!(output.combined_output(), "Already type checked.");
|
|
let building_fast_check_msg = "Building fast check graph";
|
|
assert_not_contains!(output.combined_output(), building_fast_check_msg);
|
|
|
|
// now validated
|
|
type_check_cache_path.remove_file();
|
|
let output = check_debug_cmd.run();
|
|
assert_contains!(output.combined_output(), building_fast_check_msg);
|
|
assert_contains!(
|
|
output.combined_output(),
|
|
"Using FastCheck cache for: @denotest/add@1.0.0"
|
|
);
|
|
|
|
// cause a fast check error in the nested package
|
|
nested_pkg_file
|
|
.append("\nexport function asdf(a: number) { let err: number = ''; return Math.random(); }");
|
|
check_debug_cmd.run().skip_output_check();
|
|
|
|
// ensure the cache still picks it up for this file
|
|
type_check_cache_path.remove_file();
|
|
let output = check_debug_cmd.run();
|
|
assert_contains!(output.combined_output(), building_fast_check_msg);
|
|
assert_contains!(
|
|
output.combined_output(),
|
|
"Using FastCheck cache for: @denotest/add@1.0.0"
|
|
);
|
|
|
|
// see that the type checking error in the internal function gets surfaced with --all
|
|
test_context
|
|
.new_command()
|
|
.args("check --all main.ts")
|
|
.run()
|
|
.assert_matches_text(
|
|
"Check file:///[WILDCARD]main.ts
|
|
error: TS2322 [ERROR]: Type 'string' is not assignable to type 'number'.
|
|
export function asdf(a: number) { let err: number = ''; return Math.random(); }
|
|
~~~
|
|
at http://127.0.0.1:4250/@denotest/add/1.0.0/other.ts:2:39
|
|
",
|
|
)
|
|
.assert_exit_code(1);
|
|
|
|
// now fix the package
|
|
nested_pkg_file.write("export function test() {}");
|
|
let output = check_debug_cmd.run();
|
|
assert_contains!(output.combined_output(), building_fast_check_msg);
|
|
assert_not_contains!(
|
|
output.combined_output(),
|
|
"Using FastCheck cache for: @denotest/add@1.0.0"
|
|
);
|
|
|
|
// finally ensure it uses the cache
|
|
type_check_cache_path.remove_file();
|
|
let output = check_debug_cmd.run();
|
|
assert_contains!(output.combined_output(), building_fast_check_msg);
|
|
assert_contains!(
|
|
output.combined_output(),
|
|
"Using FastCheck cache for: @denotest/add@1.0.0"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn specifiers_in_lockfile() {
|
|
let test_context = TestContextBuilder::for_jsr().use_temp_cwd().build();
|
|
let temp_dir = test_context.temp_dir();
|
|
|
|
temp_dir.write(
|
|
"main.ts",
|
|
r#"import version from "jsr:@denotest/no-module-graph@0.1";
|
|
|
|
console.log(version);"#,
|
|
);
|
|
temp_dir.write("deno.json", "{}"); // to automatically create a lockfile
|
|
|
|
test_context
|
|
.new_command()
|
|
.args("run --quiet main.ts")
|
|
.run()
|
|
.assert_matches_text("0.1.1\n");
|
|
|
|
let lockfile_path = temp_dir.path().join("deno.lock");
|
|
let mut lockfile = Lockfile::new(NewLockfileOptions {
|
|
file_path: lockfile_path.to_path_buf(),
|
|
content: &lockfile_path.read_to_string(),
|
|
overwrite: false,
|
|
})
|
|
.unwrap();
|
|
*lockfile
|
|
.content
|
|
.packages
|
|
.specifiers
|
|
.get_mut(
|
|
&JsrDepPackageReq::from_str("jsr:@denotest/no-module-graph@0.1").unwrap(),
|
|
)
|
|
.unwrap() = "0.1.0".to_string();
|
|
lockfile_path.write(lockfile.as_json_string());
|
|
|
|
test_context
|
|
.new_command()
|
|
.args("run --quiet main.ts")
|
|
.run()
|
|
.assert_matches_text("0.1.0\n");
|
|
}
|
|
|
|
#[test]
|
|
fn reload_info_not_found_cache_but_exists_remote() {
|
|
fn remove_version(registry_json: &mut Value, version: &str) {
|
|
registry_json
|
|
.as_object_mut()
|
|
.unwrap()
|
|
.get_mut("versions")
|
|
.unwrap()
|
|
.as_object_mut()
|
|
.unwrap()
|
|
.remove(version);
|
|
}
|
|
|
|
fn remove_version_for_package(
|
|
deno_dir: &util::TempDir,
|
|
package: &str,
|
|
version: &str,
|
|
) {
|
|
let specifier =
|
|
Url::parse(&format!("http://127.0.0.1:4250/{}/meta.json", package))
|
|
.unwrap();
|
|
let cache = deno_cache_dir::GlobalHttpCache::new(
|
|
deno_dir.path().join("deps").to_path_buf(),
|
|
deno_cache_dir::TestRealDenoCacheEnv,
|
|
);
|
|
let entry = cache
|
|
.get(&cache.cache_item_key(&specifier).unwrap(), None)
|
|
.unwrap()
|
|
.unwrap();
|
|
let mut registry_json: serde_json::Value =
|
|
serde_json::from_slice(&entry.content).unwrap();
|
|
remove_version(&mut registry_json, version);
|
|
cache
|
|
.set(
|
|
&specifier,
|
|
entry.metadata.headers.clone(),
|
|
registry_json.to_string().as_bytes(),
|
|
)
|
|
.unwrap();
|
|
}
|
|
|
|
// This tests that when a local machine doesn't have a version
|
|
// specified in a dependency that exists in the npm registry
|
|
let test_context = TestContextBuilder::for_jsr().use_temp_cwd().build();
|
|
let deno_dir = test_context.deno_dir();
|
|
let temp_dir = test_context.temp_dir();
|
|
temp_dir.write(
|
|
"main.ts",
|
|
"import { add } from 'jsr:@denotest/add@1'; console.log(add(1, 2));",
|
|
);
|
|
|
|
// cache successfully to the deno_dir
|
|
let output = test_context.new_command().args("cache main.ts").run();
|
|
output.assert_matches_text(concat!(
|
|
"Download http://127.0.0.1:4250/@denotest/add/meta.json\n",
|
|
"Download http://127.0.0.1:4250/@denotest/add/1.0.0_meta.json\n",
|
|
"Download http://127.0.0.1:4250/@denotest/add/1.0.0/mod.ts\n",
|
|
));
|
|
|
|
// modify the package information in the cache to remove the latest version
|
|
remove_version_for_package(deno_dir, "@denotest/add", "1.0.0");
|
|
|
|
// should error when `--cache-only` is used now because the version is not in the cache
|
|
let output = test_context
|
|
.new_command()
|
|
.args("run --cached-only main.ts")
|
|
.run();
|
|
output.assert_exit_code(1);
|
|
output.assert_matches_text("error: JSR package manifest for '@denotest/add' failed to load. Could not resolve version constraint using only cached data. Try running again without --cached-only
|
|
at file:///[WILDCARD]main.ts:1:21
|
|
");
|
|
|
|
// now try running without it, it should download the package now
|
|
test_context
|
|
.new_command()
|
|
.args("run main.ts")
|
|
.run()
|
|
.assert_matches_text(concat!(
|
|
"Download http://127.0.0.1:4250/@denotest/add/meta.json\n",
|
|
"Download http://127.0.0.1:4250/@denotest/add/1.0.0_meta.json\n",
|
|
"3\n",
|
|
))
|
|
.assert_exit_code(0);
|
|
}
|
|
|
|
#[test]
|
|
fn lockfile_bad_package_integrity() {
|
|
let test_context = TestContextBuilder::for_jsr().use_temp_cwd().build();
|
|
let temp_dir = test_context.temp_dir();
|
|
|
|
temp_dir.write(
|
|
"main.ts",
|
|
r#"import version from "jsr:@denotest/no-module-graph@0.1";
|
|
|
|
console.log(version);"#,
|
|
);
|
|
temp_dir.write("deno.json", "{}"); // to automatically create a lockfile
|
|
|
|
test_context
|
|
.new_command()
|
|
.args("run --quiet main.ts")
|
|
.run()
|
|
.assert_matches_text("0.1.1\n");
|
|
|
|
let lockfile_path = temp_dir.path().join("deno.lock");
|
|
let mut lockfile = Lockfile::new(NewLockfileOptions {
|
|
file_path: lockfile_path.to_path_buf(),
|
|
content: &lockfile_path.read_to_string(),
|
|
overwrite: false,
|
|
})
|
|
.unwrap();
|
|
let pkg_nv = "@denotest/no-module-graph@0.1.1";
|
|
let original_integrity = get_lockfile_pkg_integrity(&lockfile, pkg_nv);
|
|
set_lockfile_pkg_integrity(&mut lockfile, pkg_nv, "bad_integrity");
|
|
lockfile_path.write(lockfile.as_json_string());
|
|
|
|
let actual_integrity =
|
|
test_context.get_jsr_package_integrity("@denotest/no-module-graph/0.1.1");
|
|
let integrity_check_failed_msg = format!("[WILDCARD]Integrity check failed for package. The source code is invalid, as it does not match the expected hash in the lock file.
|
|
|
|
Package: @denotest/no-module-graph@0.1.1
|
|
Actual: {}
|
|
Expected: bad_integrity
|
|
|
|
This could be caused by:
|
|
* the lock file may be corrupt
|
|
* the source itself may be corrupt
|
|
|
|
Investigate the lockfile; delete it to regenerate the lockfile or --reload to reload the source code from the server.
|
|
", actual_integrity);
|
|
test_context
|
|
.new_command()
|
|
.args("run --quiet main.ts")
|
|
.run()
|
|
.assert_matches_text(&integrity_check_failed_msg)
|
|
.assert_exit_code(10);
|
|
|
|
// now try with a vendor folder
|
|
temp_dir
|
|
.path()
|
|
.join("deno.json")
|
|
.write_json(&json!({ "vendor": true }));
|
|
|
|
// should fail again
|
|
test_context
|
|
.new_command()
|
|
.args("run --quiet main.ts")
|
|
.run()
|
|
.assert_matches_text(&integrity_check_failed_msg)
|
|
.assert_exit_code(10);
|
|
|
|
// now update to the correct integrity
|
|
set_lockfile_pkg_integrity(&mut lockfile, pkg_nv, &original_integrity);
|
|
lockfile_path.write(lockfile.as_json_string());
|
|
|
|
// should pass now
|
|
test_context
|
|
.new_command()
|
|
.args("run --quiet main.ts")
|
|
.run()
|
|
.assert_matches_text("0.1.1\n")
|
|
.assert_exit_code(0);
|
|
|
|
// now update to a bad integrity again
|
|
set_lockfile_pkg_integrity(&mut lockfile, pkg_nv, "bad_integrity");
|
|
lockfile_path.write(lockfile.as_json_string());
|
|
|
|
// shouldn't matter because we have a vendor folder
|
|
test_context
|
|
.new_command()
|
|
.args("run --quiet main.ts")
|
|
.run()
|
|
.assert_matches_text("0.1.1\n")
|
|
.assert_exit_code(0);
|
|
|
|
// now remove the vendor dir and it should fail again
|
|
temp_dir.path().join("vendor").remove_dir_all();
|
|
|
|
test_context
|
|
.new_command()
|
|
.args("run --quiet main.ts")
|
|
.run()
|
|
.assert_matches_text(&integrity_check_failed_msg)
|
|
.assert_exit_code(10);
|
|
}
|
|
|
|
#[test]
|
|
fn bad_manifest_checksum() {
|
|
let test_context = TestContextBuilder::for_jsr().use_temp_cwd().build();
|
|
let temp_dir = test_context.temp_dir();
|
|
|
|
temp_dir.write(
|
|
"main.ts",
|
|
r#"import { add } from "jsr:@denotest/bad-manifest-checksum@1.0.0";
|
|
console.log(add);"#,
|
|
);
|
|
|
|
// test it properly checks the checksum on download
|
|
test_context
|
|
.new_command()
|
|
.args("run main.ts")
|
|
.run()
|
|
.assert_matches_text(
|
|
"Download http://127.0.0.1:4250/@denotest/bad-manifest-checksum/meta.json
|
|
Download http://127.0.0.1:4250/@denotest/bad-manifest-checksum/1.0.0_meta.json
|
|
Download http://127.0.0.1:4250/@denotest/bad-manifest-checksum/1.0.0/mod.ts
|
|
error: Integrity check failed in package. The package may have been tampered with.
|
|
|
|
Specifier: http://127.0.0.1:4250/@denotest/bad-manifest-checksum/1.0.0/mod.ts
|
|
Actual: 9a30ac96b5d5c1b67eca69e1e2cf0798817d9578c8d7d904a81a67b983b35cba
|
|
Expected: bad-checksum
|
|
|
|
If you modified your global cache, run again with the --reload flag to restore its state. If you want to modify dependencies locally run again with the --vendor flag or specify `\"vendor\": true` in a deno.json then modify the contents of the vendor/ folder.
|
|
",
|
|
)
|
|
.assert_exit_code(10);
|
|
|
|
// test it properly checks the checksum when loading from the cache
|
|
test_context
|
|
.new_command()
|
|
.args("run main.ts")
|
|
.run()
|
|
.assert_matches_text(
|
|
"error: Integrity check failed in package. The package may have been tampered with.
|
|
|
|
Specifier: http://127.0.0.1:4250/@denotest/bad-manifest-checksum/1.0.0/mod.ts
|
|
Actual: 9a30ac96b5d5c1b67eca69e1e2cf0798817d9578c8d7d904a81a67b983b35cba
|
|
Expected: bad-checksum
|
|
|
|
If you modified your global cache, run again with the --reload flag to restore its state. If you want to modify dependencies locally run again with the --vendor flag or specify `\"vendor\": true` in a deno.json then modify the contents of the vendor/ folder.
|
|
",
|
|
)
|
|
.assert_exit_code(10);
|
|
}
|
|
|
|
fn get_lockfile_pkg_integrity(lockfile: &Lockfile, pkg_nv: &str) -> String {
|
|
lockfile
|
|
.content
|
|
.packages
|
|
.jsr
|
|
.get(&PackageNv::from_str(pkg_nv).unwrap())
|
|
.unwrap()
|
|
.integrity
|
|
.clone()
|
|
}
|
|
|
|
fn set_lockfile_pkg_integrity(
|
|
lockfile: &mut Lockfile,
|
|
pkg_nv: &str,
|
|
integrity: &str,
|
|
) {
|
|
lockfile
|
|
.content
|
|
.packages
|
|
.jsr
|
|
.get_mut(&PackageNv::from_str(pkg_nv).unwrap())
|
|
.unwrap()
|
|
.integrity = integrity.to_string();
|
|
}
|