diff --git a/pacquet/tasks/integrated-benchmark/src/cli_args.rs b/pacquet/tasks/integrated-benchmark/src/cli_args.rs index 31487db546..1be9cabc8b 100644 --- a/pacquet/tasks/integrated-benchmark/src/cli_args.rs +++ b/pacquet/tasks/integrated-benchmark/src/cli_args.rs @@ -12,6 +12,10 @@ pub struct CliArgs { #[clap(long, short, default_value = "http://localhost:4873")] pub registry: String, + /// Automatically launch verdaccio if local registry doesn't response. + #[clap(long, short = 'V')] + pub verdaccio: bool, + /// Path to the git repository of pacquet. #[clap(long, short = 'R', default_value = ".")] pub repository: PathBuf, diff --git a/pacquet/tasks/integrated-benchmark/src/main.rs b/pacquet/tasks/integrated-benchmark/src/main.rs index b364b407fb..f5dc27f54f 100644 --- a/pacquet/tasks/integrated-benchmark/src/main.rs +++ b/pacquet/tasks/integrated-benchmark/src/main.rs @@ -1,5 +1,6 @@ mod cli_args; mod fixtures; +mod verdaccio; mod verify; mod work_env; @@ -8,6 +9,7 @@ async fn main() { let cli_args::CliArgs { scenario, registry, + verdaccio, repository, package_json, hyperfine_options, @@ -20,7 +22,22 @@ async fn main() { std::fs::create_dir_all(&work_env).expect("create work env"); } let work_env = std::fs::canonicalize(work_env).expect("get absolute path to work env"); - verify::ensure_virtual_registry(®istry).await; + let verdaccio = if verdaccio { + verify::ensure_program("verdaccio"); + verdaccio::VerdaccioOptions { + client: &Default::default(), + listen: ®istry, + stdout: &work_env.join("verdaccio.stdout.log"), + stderr: &work_env.join("verdaccio.stderr.log"), + max_retries: 10, + retry_delay: tokio::time::Duration::from_millis(500), + } + .spawn_if_necessary() + .await + } else { + verify::ensure_virtual_registry(®istry).await; + None + }; verify::ensure_git_repo(&repository); verify::validate_revision_list(&revisions); verify::ensure_program("bash"); @@ -39,4 +56,5 @@ async fn main() { package_json, } .run(); + drop(verdaccio); // terminate verdaccio if exists } diff --git a/pacquet/tasks/integrated-benchmark/src/verdaccio.rs b/pacquet/tasks/integrated-benchmark/src/verdaccio.rs new file mode 100644 index 0000000000..8ce418933d --- /dev/null +++ b/pacquet/tasks/integrated-benchmark/src/verdaccio.rs @@ -0,0 +1,114 @@ +use crate::verify::ensure_program; +use pipe_trait::Pipe; +use reqwest::Client; +use std::{ + fs::File, + path::Path, + process::{Child, Command, Stdio}, +}; +use tokio::time::{sleep, Duration}; + +#[derive(Debug)] +pub struct Verdaccio { + process: Child, +} + +impl Drop for Verdaccio { + fn drop(&mut self) { + let Verdaccio { process } = self; + let pid = process.id(); + + eprintln!("info: Terminating verdaccio with the kill command (kill {pid})..."); + match Command::new("kill").arg(process.id().to_string()).output() { + Err(error) => { + eprintln!("warn: Failed to terminate verdaccio with the kill command: {error}"); + } + Ok(output) => { + if output.status.success() { + eprintln!("info: Verdaccio terminated"); + return; + } + + let stderr = String::from_utf8_lossy(&output.stderr); + eprintln!("warn: Failed to terminate verdaccio with the kill command: {stderr}"); + } + } + + eprintln!("info: Terminating verdaccio with SIGKILL..."); + if let Err(error) = process.kill() { + eprintln!("warn: Failed to terminate verdaccio with SIGKILL: {error}"); + } else { + eprintln!("info: Verdaccio terminated"); + } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct VerdaccioOptions<'a> { + pub client: &'a Client, + pub listen: &'a str, + pub stdout: &'a Path, + pub stderr: &'a Path, + pub max_retries: usize, + pub retry_delay: Duration, +} + +impl<'a> VerdaccioOptions<'a> { + async fn is_registry_ready(self) -> bool { + let VerdaccioOptions { client, listen, .. } = self; + + let Err(error) = client.head(listen).send().await else { + return true; + }; + + if error.is_connect() { + eprintln!("info: {error}"); + return false; + } + + panic!("{error}"); + } + + async fn wait_for_registry(self) { + let VerdaccioOptions { max_retries, retry_delay, .. } = self; + let mut retries = max_retries; + + while !self.is_registry_ready().await { + retries = retries.checked_sub(1).unwrap_or_else(|| { + panic!("Failed to check for the registry for {max_retries} times") + }); + + sleep(retry_delay).await; + } + } + + async fn spawn(self) -> Verdaccio { + let VerdaccioOptions { listen, stdout, stderr, .. } = self; + + ensure_program("verdaccio"); + + let process = Command::new("verdaccio") + .arg("--listen") + .arg(listen) + .stdin(Stdio::null()) + .stdout(File::create(stdout).expect("create file for stdout")) + .stderr(File::create(stderr).expect("create file for stderr")) + .spawn() + .expect("spawn verdaccio"); + + self.wait_for_registry().await; + + Verdaccio { process } + } + + pub async fn spawn_if_necessary(self) -> Option { + let VerdaccioOptions { listen, .. } = self; + if self.is_registry_ready().await { + eprintln!("info: {listen} is already available"); + None + } else { + eprintln!("info: spawning verdaccio..."); + self.spawn().await.pipe(Some) + } + } +}