0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-03-03 17:34:47 -05:00

refactor(installer): refactor installer code to be more testable (#13374)

This commit is contained in:
David Sherret 2022-01-14 13:23:47 -05:00 committed by GitHub
parent dc58063d00
commit 903cb48fe9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -46,11 +46,12 @@ fn validate_name(exec_name: &str) -> Result<(), AnyError> {
/// One compatible with cmd & powershell with a .cmd extension /// One compatible with cmd & powershell with a .cmd extension
/// A second compatible with git bash / MINGW64 /// A second compatible with git bash / MINGW64
/// Generate batch script to satisfy that. /// Generate batch script to satisfy that.
fn generate_executable_file( fn generate_executable_file(shim_data: &ShimData) -> Result<(), AnyError> {
mut file_path: PathBuf, let args: Vec<String> = shim_data
args: Vec<String>, .args
) -> Result<(), AnyError> { .iter()
let args: Vec<String> = args.iter().map(|c| format!("\"{}\"", c)).collect(); .map(|c| format!("\"{}\"", c))
.collect();
let template = format!( let template = format!(
"% generated by deno install %\n@deno {} %*\n", "% generated by deno install %\n@deno {} %*\n",
args args
@ -59,12 +60,11 @@ fn generate_executable_file(
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(" ") .join(" ")
); );
let mut file = File::create(&file_path)?; let mut file = File::create(&shim_data.file_path)?;
file.write_all(template.as_bytes())?; file.write_all(template.as_bytes())?;
// write file for bash // write file for bash
// create filepath without extensions // create filepath without extensions
file_path.set_extension("");
let template = format!( let template = format!(
r#"#!/bin/sh r#"#!/bin/sh
# generated by deno install # generated by deno install
@ -72,19 +72,17 @@ deno {} "$@"
"#, "#,
args.join(" "), args.join(" "),
); );
let mut file = File::create(&file_path)?; let mut file = File::create(&shim_data.file_path.with_extension(""))?;
file.write_all(template.as_bytes())?; file.write_all(template.as_bytes())?;
Ok(()) Ok(())
} }
#[cfg(not(windows))] #[cfg(not(windows))]
fn generate_executable_file( fn generate_executable_file(shim_data: &ShimData) -> Result<(), AnyError> {
file_path: PathBuf,
args: Vec<String>,
) -> Result<(), AnyError> {
use shell_escape::escape; use shell_escape::escape;
let args: Vec<String> = args let args: Vec<String> = shim_data
.into_iter() .args
.iter()
.map(|c| escape(c.into()).into_owned()) .map(|c| escape(c.into()).into_owned())
.collect(); .collect();
let template = format!( let template = format!(
@ -94,12 +92,12 @@ exec deno {} "$@"
"#, "#,
args.join(" "), args.join(" "),
); );
let mut file = File::create(&file_path)?; let mut file = File::create(&shim_data.file_path)?;
file.write_all(template.as_bytes())?; file.write_all(template.as_bytes())?;
let _metadata = fs::metadata(&file_path)?; let _metadata = fs::metadata(&shim_data.file_path)?;
let mut permissions = _metadata.permissions(); let mut permissions = _metadata.permissions();
permissions.set_mode(0o755); permissions.set_mode(0o755);
fs::set_permissions(&file_path, permissions)?; fs::set_permissions(&shim_data.file_path, permissions)?;
Ok(()) Ok(())
} }
@ -195,27 +193,73 @@ pub fn install(
flags: Flags, flags: Flags,
install_flags: InstallFlags, install_flags: InstallFlags,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let root = if let Some(root) = install_flags.root { let shim_data = resolve_shim_data(&flags, &install_flags)?;
canonicalize_path(&root)?
} else {
get_installer_root()?
};
let installation_dir = root.join("bin");
// ensure directory exists // ensure directory exists
if let Ok(metadata) = fs::metadata(&installation_dir) { if let Ok(metadata) = fs::metadata(&shim_data.installation_dir) {
if !metadata.is_dir() { if !metadata.is_dir() {
return Err(generic_error("Installation path is not a directory")); return Err(generic_error("Installation path is not a directory"));
} }
} else { } else {
fs::create_dir_all(&installation_dir)?; fs::create_dir_all(&shim_data.installation_dir)?;
}; };
if shim_data.file_path.exists() && !install_flags.force {
return Err(generic_error(
"Existing installation found. Aborting (Use -f to overwrite).",
));
};
generate_executable_file(&shim_data)?;
for (path, contents) in shim_data.extra_files {
fs::write(path, contents)?;
}
println!("✅ Successfully installed {}", shim_data.name);
println!("{}", shim_data.file_path.display());
if cfg!(windows) {
let display_path = shim_data.file_path.with_extension("");
println!("{} (shell)", display_path.display());
}
let installation_dir_str = shim_data.installation_dir.to_string_lossy();
if !is_in_path(&shim_data.installation_dir) {
println!(" Add {} to PATH", installation_dir_str);
if cfg!(windows) {
println!(" set PATH=%PATH%;{}", installation_dir_str);
} else {
println!(" export PATH=\"{}:$PATH\"", installation_dir_str);
}
}
Ok(())
}
struct ShimData {
name: String,
installation_dir: PathBuf,
file_path: PathBuf,
args: Vec<String>,
extra_files: Vec<(PathBuf, String)>,
}
fn resolve_shim_data(
flags: &Flags,
install_flags: &InstallFlags,
) -> Result<ShimData, AnyError> {
let root = if let Some(root) = &install_flags.root {
canonicalize_path(root)?
} else {
get_installer_root()?
};
let installation_dir = root.join("bin");
// Check if module_url is remote // Check if module_url is remote
let module_url = resolve_url_or_path(&install_flags.module_url)?; let module_url = resolve_url_or_path(&install_flags.module_url)?;
let name = install_flags let name = install_flags
.name .name
.clone()
.or_else(|| infer_name_from_url(&module_url)); .or_else(|| infer_name_from_url(&module_url));
let name = match name { let name = match name {
@ -232,12 +276,6 @@ pub fn install(
file_path = file_path.with_extension("cmd"); file_path = file_path.with_extension("cmd");
} }
if file_path.exists() && !install_flags.force {
return Err(generic_error(
"Existing installation found. Aborting (Use -f to overwrite).",
));
};
let mut extra_files: Vec<(PathBuf, String)> = vec![]; let mut extra_files: Vec<(PathBuf, String)> = vec![];
let mut executable_args = vec!["run".to_string()]; let mut executable_args = vec!["run".to_string()];
@ -246,9 +284,9 @@ pub fn install(
executable_args.push("--location".to_string()); executable_args.push("--location".to_string());
executable_args.push(url.to_string()); executable_args.push(url.to_string());
} }
if let Some(ca_file) = flags.ca_file { if let Some(ca_file) = &flags.ca_file {
executable_args.push("--cert".to_string()); executable_args.push("--cert".to_string());
executable_args.push(ca_file) executable_args.push(ca_file.to_owned())
} }
if let Some(log_level) = flags.log_level { if let Some(log_level) = flags.log_level {
if log_level == Level::Error { if log_level == Level::Error {
@ -311,13 +349,13 @@ pub fn install(
executable_args.push(format!("--inspect-brk={}", inspect_brk.to_string())); executable_args.push(format!("--inspect-brk={}", inspect_brk.to_string()));
} }
if let Some(import_map_path) = flags.import_map_path { if let Some(import_map_path) = &flags.import_map_path {
let import_map_url = resolve_url_or_path(&import_map_path)?; let import_map_url = resolve_url_or_path(import_map_path)?;
executable_args.push("--import-map".to_string()); executable_args.push("--import-map".to_string());
executable_args.push(import_map_url.to_string()); executable_args.push(import_map_url.to_string());
} }
if let Some(config_path) = flags.config_path { if let Some(config_path) = &flags.config_path {
let mut copy_path = file_path.clone(); let mut copy_path = file_path.clone();
copy_path.set_extension("tsconfig.json"); copy_path.set_extension("tsconfig.json");
executable_args.push("--config".to_string()); executable_args.push("--config".to_string());
@ -325,7 +363,7 @@ pub fn install(
extra_files.push((copy_path, fs::read_to_string(config_path)?)); extra_files.push((copy_path, fs::read_to_string(config_path)?));
} }
if let Some(lock_path) = flags.lock { if let Some(lock_path) = &flags.lock {
let mut copy_path = file_path.clone(); let mut copy_path = file_path.clone();
copy_path.set_extension("lock.json"); copy_path.set_extension("lock.json");
executable_args.push("--lock".to_string()); executable_args.push("--lock".to_string());
@ -336,29 +374,13 @@ pub fn install(
executable_args.push(module_url.to_string()); executable_args.push(module_url.to_string());
executable_args.extend_from_slice(&install_flags.args); executable_args.extend_from_slice(&install_flags.args);
generate_executable_file(file_path.to_owned(), executable_args)?; Ok(ShimData {
for (path, contents) in extra_files { name,
fs::write(path, contents)?; installation_dir,
} file_path,
args: executable_args,
println!("✅ Successfully installed {}", name); extra_files,
println!("{}", file_path.to_string_lossy()); })
if cfg!(windows) {
file_path.set_extension("");
println!("{} (shell)", file_path.to_string_lossy());
}
let installation_dir_str = installation_dir.to_string_lossy();
if !is_in_path(&installation_dir) {
println!(" Add {} to PATH", installation_dir_str);
if cfg!(windows) {
println!(" set PATH=%PATH%;{}", installation_dir_str);
} else {
println!(" export PATH=\"{}:$PATH\"", installation_dir_str);
}
}
Ok(())
} }
fn is_in_path(dir: &Path) -> bool { fn is_in_path(dir: &Path) -> bool {
@ -484,6 +506,16 @@ mod tests {
) )
.expect("Install failed"); .expect("Install failed");
if let Some(home) = original_home {
env::set_var("HOME", home);
}
if let Some(user_profile) = original_user_profile {
env::set_var("USERPROFILE", user_profile);
}
if let Some(install_root) = original_install_root {
env::set_var("DENO_INSTALL_ROOT", install_root);
}
let mut file_path = temp_dir.path().join(".deno/bin/echo_test"); let mut file_path = temp_dir.path().join(".deno/bin/echo_test");
assert!(file_path.exists()); assert!(file_path.exists());
@ -502,15 +534,6 @@ mod tests {
} else { } else {
assert!(content.contains(r#"run 'http://localhost:4545/echo_server.ts'"#)); assert!(content.contains(r#"run 'http://localhost:4545/echo_server.ts'"#));
} }
if let Some(home) = original_home {
env::set_var("HOME", home);
}
if let Some(user_profile) = original_user_profile {
env::set_var("USERPROFILE", user_profile);
}
if let Some(install_root) = original_install_root {
env::set_var("DENO_INSTALL_ROOT", install_root);
}
} }
#[test] #[test]
@ -554,142 +577,66 @@ mod tests {
} }
#[test] #[test]
fn install_prompt() { fn install_inferred_name() {
let temp_dir = TempDir::new().expect("tempdir fail"); let shim_data = resolve_shim_data(
let bin_dir = temp_dir.path().join("bin"); &Flags::default(),
std::fs::create_dir(&bin_dir).unwrap(); &InstallFlags {
install(
Flags {
prompt: true,
..Flags::default()
},
InstallFlags {
module_url: "http://localhost:4545/echo_server.ts".to_string(), module_url: "http://localhost:4545/echo_server.ts".to_string(),
args: vec![], args: vec![],
name: Some("echo_test".to_string()), name: None,
root: Some(temp_dir.path().to_path_buf()), root: Some(env::temp_dir()),
force: false, force: false,
}, },
) )
.unwrap(); .unwrap();
let mut file_path = bin_dir.join("echo_test"); assert_eq!(shim_data.name, "echo_server");
if cfg!(windows) { assert_eq!(
file_path = file_path.with_extension("cmd"); shim_data.args,
} vec!["run", "http://localhost:4545/echo_server.ts",]
);
let content = fs::read_to_string(file_path).unwrap();
if cfg!(windows) {
assert!(content.contains(
r#""run" "--prompt" "http://localhost:4545/echo_server.ts""#
));
} else {
assert!(content
.contains(r#"run --prompt 'http://localhost:4545/echo_server.ts'"#));
}
}
#[test]
fn install_inferred_name() {
let temp_dir = TempDir::new().expect("tempdir fail");
let bin_dir = temp_dir.path().join("bin");
std::fs::create_dir(&bin_dir).unwrap();
install(
Flags::default(),
InstallFlags {
module_url: "http://localhost:4545/echo_server.ts".to_string(),
args: vec![],
name: None,
root: Some(temp_dir.path().to_path_buf()),
force: false,
},
)
.expect("Install failed");
let mut file_path = bin_dir.join("echo_server");
if cfg!(windows) {
file_path = file_path.with_extension("cmd");
}
assert!(file_path.exists());
let content = fs::read_to_string(file_path).unwrap();
if cfg!(windows) {
assert!(
content.contains(r#""run" "http://localhost:4545/echo_server.ts""#)
);
} else {
assert!(content.contains(r#"run 'http://localhost:4545/echo_server.ts'"#));
}
} }
#[test] #[test]
fn install_inferred_name_from_parent() { fn install_inferred_name_from_parent() {
let temp_dir = TempDir::new().expect("tempdir fail"); let shim_data = resolve_shim_data(
let bin_dir = temp_dir.path().join("bin"); &Flags::default(),
std::fs::create_dir(&bin_dir).unwrap(); &InstallFlags {
install(
Flags::default(),
InstallFlags {
module_url: "http://localhost:4545/subdir/main.ts".to_string(), module_url: "http://localhost:4545/subdir/main.ts".to_string(),
args: vec![], args: vec![],
name: None, name: None,
root: Some(temp_dir.path().to_path_buf()), root: Some(env::temp_dir()),
force: false, force: false,
}, },
) )
.expect("Install failed"); .unwrap();
let mut file_path = bin_dir.join("subdir"); assert_eq!(shim_data.name, "subdir");
if cfg!(windows) { assert_eq!(
file_path = file_path.with_extension("cmd"); shim_data.args,
} vec!["run", "http://localhost:4545/subdir/main.ts",]
);
assert!(file_path.exists());
let content = fs::read_to_string(file_path).unwrap();
if cfg!(windows) {
assert!(
content.contains(r#""run" "http://localhost:4545/subdir/main.ts""#)
);
} else {
assert!(content.contains(r#"run 'http://localhost:4545/subdir/main.ts'"#));
}
} }
#[test] #[test]
fn install_custom_dir_option() { fn install_custom_dir_option() {
let temp_dir = TempDir::new().expect("tempdir fail"); let shim_data = resolve_shim_data(
let bin_dir = temp_dir.path().join("bin"); &Flags::default(),
std::fs::create_dir(&bin_dir).unwrap(); &InstallFlags {
install(
Flags::default(),
InstallFlags {
module_url: "http://localhost:4545/echo_server.ts".to_string(), module_url: "http://localhost:4545/echo_server.ts".to_string(),
args: vec![], args: vec![],
name: Some("echo_test".to_string()), name: Some("echo_test".to_string()),
root: Some(temp_dir.path().to_path_buf()), root: Some(env::temp_dir()),
force: false, force: false,
}, },
) )
.expect("Install failed"); .unwrap();
let mut file_path = bin_dir.join("echo_test"); assert_eq!(shim_data.name, "echo_test");
if cfg!(windows) { assert_eq!(
file_path = file_path.with_extension("cmd"); shim_data.args,
} vec!["run", "http://localhost:4545/echo_server.ts",]
);
assert!(file_path.exists());
let content = fs::read_to_string(file_path).unwrap();
if cfg!(windows) {
assert!(
content.contains(r#""run" "http://localhost:4545/echo_server.ts""#)
);
} else {
assert!(content.contains(r#"run 'http://localhost:4545/echo_server.ts'"#));
}
} }
#[test] #[test]
@ -701,9 +648,9 @@ mod tests {
let original_install_root = env::var_os("DENO_INSTALL_ROOT"); let original_install_root = env::var_os("DENO_INSTALL_ROOT");
env::set_var("DENO_INSTALL_ROOT", temp_dir.path().to_path_buf()); env::set_var("DENO_INSTALL_ROOT", temp_dir.path().to_path_buf());
install( let shim_data = resolve_shim_data(
Flags::default(), &Flags::default(),
InstallFlags { &InstallFlags {
module_url: "http://localhost:4545/echo_server.ts".to_string(), module_url: "http://localhost:4545/echo_server.ts".to_string(),
args: vec![], args: vec![],
name: Some("echo_test".to_string()), name: Some("echo_test".to_string()),
@ -711,100 +658,102 @@ mod tests {
force: false, force: false,
}, },
) )
.expect("Install failed"); .unwrap();
let mut file_path = bin_dir.join("echo_test");
if cfg!(windows) {
file_path = file_path.with_extension("cmd");
}
assert!(file_path.exists());
let content = fs::read_to_string(file_path).unwrap();
if cfg!(windows) {
assert!(
content.contains(r#""run" "http://localhost:4545/echo_server.ts""#)
);
} else {
assert!(content.contains(r#"run 'http://localhost:4545/echo_server.ts'"#));
}
if let Some(install_root) = original_install_root { if let Some(install_root) = original_install_root {
env::set_var("DENO_INSTALL_ROOT", install_root); env::set_var("DENO_INSTALL_ROOT", install_root);
} }
assert_eq!(
fs::canonicalize(shim_data.installation_dir).unwrap(),
fs::canonicalize(bin_dir).unwrap()
);
assert_eq!(shim_data.name, "echo_test");
assert_eq!(
shim_data.args,
vec!["run", "http://localhost:4545/echo_server.ts",]
);
} }
#[test] #[test]
fn install_with_flags() { fn install_with_flags() {
let temp_dir = TempDir::new().expect("tempdir fail"); let shim_data = resolve_shim_data(
let bin_dir = temp_dir.path().join("bin"); &Flags {
std::fs::create_dir(&bin_dir).unwrap();
install(
Flags {
allow_net: Some(vec![]), allow_net: Some(vec![]),
allow_read: Some(vec![]), allow_read: Some(vec![]),
check: CheckFlag::None, check: CheckFlag::None,
log_level: Some(Level::Error), log_level: Some(Level::Error),
..Flags::default() ..Flags::default()
}, },
InstallFlags { &InstallFlags {
module_url: "http://localhost:4545/echo_server.ts".to_string(), module_url: "http://localhost:4545/echo_server.ts".to_string(),
args: vec!["--foobar".to_string()], args: vec!["--foobar".to_string()],
name: Some("echo_test".to_string()), name: Some("echo_test".to_string()),
root: Some(temp_dir.path().to_path_buf()), root: Some(env::temp_dir()),
force: false,
},
)
.expect("Install failed");
let mut file_path = bin_dir.join("echo_test");
if cfg!(windows) {
file_path = file_path.with_extension("cmd");
}
assert!(file_path.exists());
let content = fs::read_to_string(file_path).unwrap();
if cfg!(windows) {
assert!(content.contains(r#""run" "--allow-read" "--allow-net" "--quiet" "--no-check" "http://localhost:4545/echo_server.ts" "--foobar""#));
} else {
assert!(content.contains(r#"run --allow-read --allow-net --quiet --no-check 'http://localhost:4545/echo_server.ts' --foobar"#));
}
}
#[test]
fn install_allow_all() {
let temp_dir = TempDir::new().expect("tempdir fail");
let bin_dir = temp_dir.path().join("bin");
std::fs::create_dir(&bin_dir).unwrap();
install(
Flags {
allow_all: true,
..Flags::default()
},
InstallFlags {
module_url: "http://localhost:4545/echo_server.ts".to_string(),
args: vec![],
name: Some("echo_test".to_string()),
root: Some(temp_dir.path().to_path_buf()),
force: false, force: false,
}, },
) )
.unwrap(); .unwrap();
let mut file_path = bin_dir.join("echo_test"); assert_eq!(shim_data.name, "echo_test");
if cfg!(windows) { assert_eq!(
file_path = file_path.with_extension("cmd"); shim_data.args,
} vec![
"run",
"--allow-read",
"--allow-net",
"--quiet",
"--no-check",
"http://localhost:4545/echo_server.ts",
"--foobar",
]
);
}
let content = fs::read_to_string(file_path).unwrap(); #[test]
if cfg!(windows) { fn install_prompt() {
assert!(content.contains( let shim_data = resolve_shim_data(
r#""run" "--allow-all" "http://localhost:4545/echo_server.ts""# &Flags {
)); prompt: true,
} else { ..Flags::default()
assert!(content },
.contains(r#"run --allow-all 'http://localhost:4545/echo_server.ts'"#)); &InstallFlags {
} module_url: "http://localhost:4545/echo_server.ts".to_string(),
args: vec![],
name: Some("echo_test".to_string()),
root: Some(env::temp_dir()),
force: false,
},
)
.unwrap();
assert_eq!(
shim_data.args,
vec!["run", "--prompt", "http://localhost:4545/echo_server.ts",]
);
}
#[test]
fn install_allow_all() {
let shim_data = resolve_shim_data(
&Flags {
allow_all: true,
..Flags::default()
},
&InstallFlags {
module_url: "http://localhost:4545/echo_server.ts".to_string(),
args: vec![],
name: Some("echo_test".to_string()),
root: Some(env::temp_dir()),
force: false,
},
)
.unwrap();
assert_eq!(
shim_data.args,
vec!["run", "--allow-all", "http://localhost:4545/echo_server.ts",]
);
} }
#[test] #[test]