mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 07:14:47 -05:00
4f80d83774
This changes the lockfile to not store JSR specifiers in the "remote" section. Instead a single JSR integrity is stored per package in the lockfile, which is a hash of the version's `x.x.x_meta.json` file, which contains hashes for every file in the package. The hashes in this file are then compared against when loading. Additionally, when using `{ "vendor": true }` in a deno.json, the files can be modified without causing lockfile errors—the checksum is only checked when copying into the vendor folder and not afterwards (eventually we should add this behaviour for non-jsr specifiers as well). As part of this change, the `vendor` folder creation is not always automatic in the LSP and running an explicit cache command is necessary. The code required to track checksums in the LSP would have been too complex for this PR, so that all goes through deno_graph now. The vendoring is still automatic when running from the CLI.
349 lines
9.8 KiB
Rust
349 lines
9.8 KiB
Rust
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
|
|
|
use deno_core::serde_json::json;
|
|
use deno_core::serde_json::Value;
|
|
use deno_lockfile::Lockfile;
|
|
use test_util as util;
|
|
use test_util::itest;
|
|
use url::Url;
|
|
use util::env_vars_for_jsr_tests;
|
|
use util::TestContextBuilder;
|
|
|
|
itest!(no_module_graph_run {
|
|
args: "run jsr/no_module_graph/main.ts",
|
|
output: "jsr/no_module_graph/main.out",
|
|
envs: env_vars_for_jsr_tests(),
|
|
http_server: true,
|
|
});
|
|
|
|
itest!(no_module_graph_info {
|
|
args: "info jsr/no_module_graph/main.ts",
|
|
output: "jsr/no_module_graph/main_info.out",
|
|
envs: env_vars_for_jsr_tests(),
|
|
http_server: true,
|
|
});
|
|
|
|
itest!(same_package_multiple_versions {
|
|
args: "run --quiet jsr/no_module_graph/multiple.ts",
|
|
output: "jsr/no_module_graph/multiple.out",
|
|
envs: env_vars_for_jsr_tests(),
|
|
http_server: true,
|
|
});
|
|
|
|
itest!(module_graph_run {
|
|
args: "run jsr/module_graph/main.ts",
|
|
output: "jsr/module_graph/main.out",
|
|
envs: env_vars_for_jsr_tests(),
|
|
http_server: true,
|
|
});
|
|
|
|
itest!(module_graph_info {
|
|
args: "info jsr/module_graph/main.ts",
|
|
output: "jsr/module_graph/main_info.out",
|
|
envs: env_vars_for_jsr_tests(),
|
|
http_server: true,
|
|
});
|
|
|
|
itest!(deps_run {
|
|
args: "run jsr/deps/main.ts",
|
|
output: "jsr/deps/main.out",
|
|
envs: env_vars_for_jsr_tests(),
|
|
http_server: true,
|
|
});
|
|
|
|
itest!(deps_info {
|
|
args: "info jsr/deps/main.ts",
|
|
output: "jsr/deps/main_info.out",
|
|
envs: env_vars_for_jsr_tests(),
|
|
http_server: true,
|
|
});
|
|
|
|
itest!(subset_type_graph {
|
|
args: "check --all jsr/subset_type_graph/main.ts",
|
|
output: "jsr/subset_type_graph/main.check.out",
|
|
envs: env_vars_for_jsr_tests(),
|
|
http_server: true,
|
|
exit_code: 1,
|
|
});
|
|
|
|
itest!(version_not_found {
|
|
args: "run jsr/version_not_found/main.ts",
|
|
output: "jsr/version_not_found/main.out",
|
|
envs: env_vars_for_jsr_tests(),
|
|
http_server: true,
|
|
exit_code: 1,
|
|
});
|
|
|
|
#[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(lockfile_path.to_path_buf(), false).unwrap();
|
|
*lockfile
|
|
.content
|
|
.packages
|
|
.specifiers
|
|
.get_mut("jsr:@denotest/no_module_graph@0.1")
|
|
.unwrap() = "jsr:@denotest/no_module_graph@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 registry_json_path = deno_dir
|
|
.path()
|
|
.join("deps")
|
|
.join(deno_cache_dir::url_to_filename(&specifier).unwrap());
|
|
let mut registry_json = registry_json_path.read_json_value();
|
|
remove_version(&mut registry_json, version);
|
|
registry_json_path.write_json(®istry_json);
|
|
}
|
|
|
|
// 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: Failed to resolve version constraint. 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(lockfile_path.to_path_buf(), false).unwrap();
|
|
let pkg_name = "@denotest/no_module_graph@0.1.1";
|
|
let original_integrity = get_lockfile_pkg_integrity(&lockfile, pkg_name);
|
|
set_lockfile_pkg_integrity(&mut lockfile, pkg_name, "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!("error: Integrity check failed for http://127.0.0.1:4250/@denotest/no_module_graph/0.1.1_meta.json
|
|
|
|
Actual: {}
|
|
Expected: bad_integrity
|
|
at file:///[WILDCARD]/main.ts:1:21
|
|
", actual_integrity);
|
|
test_context
|
|
.new_command()
|
|
.args("run --quiet main.ts")
|
|
.run()
|
|
.assert_matches_text(&integrity_check_failed_msg)
|
|
.assert_exit_code(1);
|
|
|
|
// 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(1);
|
|
|
|
// now update to the correct integrity
|
|
set_lockfile_pkg_integrity(&mut lockfile, pkg_name, &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_name, "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(1);
|
|
}
|
|
|
|
#[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.
|
|
|
|
Actual: 9a30ac96b5d5c1b67eca69e1e2cf0798817d9578c8d7d904a81a67b983b35cba
|
|
Expected: bad-checksum
|
|
at file:///[WILDCARD]main.ts:1:21
|
|
",
|
|
)
|
|
.assert_exit_code(1);
|
|
|
|
// test it properly checks the checksum when loading from the cache
|
|
test_context
|
|
.new_command()
|
|
.args("run main.ts")
|
|
.run()
|
|
.assert_matches_text(
|
|
// ideally the two error messages would be the same... this one comes from
|
|
// deno_cache and the one above comes from deno_graph. The thing is, in deno_cache
|
|
// (source of this error) it makes sense to include the url in the error message
|
|
// because it's not always used in the context of deno_graph
|
|
"error: Integrity check failed for http://127.0.0.1:4250/@denotest/bad-manifest-checksum/1.0.0/mod.ts
|
|
|
|
Actual: 9a30ac96b5d5c1b67eca69e1e2cf0798817d9578c8d7d904a81a67b983b35cba
|
|
Expected: bad-checksum
|
|
at file:///[WILDCARD]main.ts:1:21
|
|
",
|
|
)
|
|
.assert_exit_code(1);
|
|
}
|
|
|
|
fn get_lockfile_pkg_integrity(lockfile: &Lockfile, pkg_name: &str) -> String {
|
|
lockfile
|
|
.content
|
|
.packages
|
|
.jsr
|
|
.get(pkg_name)
|
|
.unwrap()
|
|
.integrity
|
|
.clone()
|
|
}
|
|
|
|
fn set_lockfile_pkg_integrity(
|
|
lockfile: &mut Lockfile,
|
|
pkg_name: &str,
|
|
integrity: &str,
|
|
) {
|
|
lockfile
|
|
.content
|
|
.packages
|
|
.jsr
|
|
.get_mut(pkg_name)
|
|
.unwrap()
|
|
.integrity = integrity.to_string();
|
|
}
|