diff --git a/Makefile b/Makefile index 36dfef6bfd7..3638fe40903 100644 --- a/Makefile +++ b/Makefile @@ -619,7 +619,7 @@ test-integration-cli: build-wasmer build-capi package-capi-headless package dist # Before running this in the CI, we need to set up link.tar.gz and /cache/wasmer-[target].tar.gz test-integration-cli-ci: rustup target add wasm32-wasi - $(CARGO_BINARY) test $(CARGO_TARGET_FLAG) --features webc_runner -p wasmer-integration-tests-cli -- --nocapture --test-threads=1 + $(CARGO_BINARY) test $(CARGO_TARGET_FLAG) --features webc_runner -p wasmer-integration-tests-cli -- --test-threads=1 test-integration-ios: $(CARGO_BINARY) test $(CARGO_TARGET_FLAG) --features webc_runner -p wasmer-integration-tests-ios diff --git a/lib/registry/src/config.rs b/lib/registry/src/config.rs index 5f24011d4ae..166a42f6a05 100644 --- a/lib/registry/src/config.rs +++ b/lib/registry/src/config.rs @@ -1,5 +1,6 @@ use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; +use url::Url; pub static GLOBAL_CONFIG_DATABASE_FILE_NAME: &str = "wasmer.sqlite"; @@ -65,22 +66,37 @@ pub struct Registry { } pub fn format_graphql(registry: &str) -> String { - let mut registry = registry.to_string(); - if registry.contains("wapm.dev") { - registry = "https://registry.wapm.dev/graphql".to_string(); - } else if registry.contains("wapm.io") { - registry = "https://registry.wapm.io/graphql".to_string(); + if let Ok(mut url) = Url::parse(registry) { + // Looks like we've got a valid URL. Let's try to use it as-is. + if url.has_host() { + if url.path() == "/" { + // make sure we convert http://registry.wapm.io/ to + // http://registry.wapm.io/graphql + url.set_path("/graphql"); + } + + return url.to_string(); + } } - if !registry.starts_with("https://") { - registry = format!("https://{registry}"); + + if !registry.contains("://") && !registry.contains('/') { + return endpoint_from_domain_name(registry); } - if registry.ends_with("/graphql") { - registry - } else if registry.ends_with('/') { - format!("{}graphql", registry) - } else { - format!("{}/graphql", registry) + + // looks like we've received something we can't deal with. Just pass it + // through as-is and hopefully it'll either work or the end user can figure + // it out + registry.to_string() +} + +/// By convention, something like `"wapm.io"` should be converted to +/// `"https://registry.wapm.io/graphql"`. +fn endpoint_from_domain_name(domain_name: &str) -> String { + if domain_name.contains("localhost") { + return format!("http://{domain_name}/graphql"); } + + format!("https://registry.{domain_name}/graphql") } fn test_if_registry_present(registry: &str) -> Result<(), String> { @@ -95,40 +111,6 @@ pub enum UpdateRegistry { LeaveAsIs, } -#[test] -fn test_registries_switch_token() { - let mut registries = MultiRegistry::default(); - - registries.set_current_registry("https://registry.wapm.dev"); - assert_eq!( - registries.get_current_registry(), - "https://registry.wapm.dev/graphql".to_string() - ); - registries.set_login_token_for_registry( - "https://registry.wapm.io", - "token1", - UpdateRegistry::LeaveAsIs, - ); - assert_eq!( - registries.get_current_registry(), - "https://registry.wapm.dev/graphql".to_string() - ); - assert_eq!( - registries.get_login_token_for_registry(®istries.get_current_registry()), - None - ); - registries.set_current_registry("https://registry.wapm.io"); - assert_eq!( - registries.get_login_token_for_registry(®istries.get_current_registry()), - Some("token1".to_string()) - ); - registries.clear_current_registry_token(); - assert_eq!( - registries.get_login_token_for_registry(®istries.get_current_registry()), - None - ); -} - impl MultiRegistry { /// Gets the current (active) registry URL pub fn clear_current_registry_token(&mut self) { @@ -264,3 +246,75 @@ impl WasmerConfig { wasmer_dir.join(GLOBAL_CONFIG_DATABASE_FILE_NAME) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_registries_switch_token() { + let mut registries = MultiRegistry::default(); + + registries.set_current_registry("https://registry.wapm.dev"); + assert_eq!( + registries.get_current_registry(), + "https://registry.wapm.dev/graphql".to_string() + ); + registries.set_login_token_for_registry( + "https://registry.wapm.io", + "token1", + UpdateRegistry::LeaveAsIs, + ); + assert_eq!( + registries.get_current_registry(), + "https://registry.wapm.dev/graphql".to_string() + ); + assert_eq!( + registries.get_login_token_for_registry(®istries.get_current_registry()), + None + ); + registries.set_current_registry("https://registry.wapm.io"); + assert_eq!( + registries.get_login_token_for_registry(®istries.get_current_registry()), + Some("token1".to_string()) + ); + registries.clear_current_registry_token(); + assert_eq!( + registries.get_login_token_for_registry(®istries.get_current_registry()), + None + ); + } + + #[test] + fn format_registry_urls() { + let inputs = [ + // Domain names work + ("wapm.io", "https://registry.wapm.io/graphql"), + ("wapm.dev", "https://registry.wapm.dev/graphql"), + // Plain URLs + ( + "https://registry.wapm.dev/graphql", + "https://registry.wapm.dev/graphql", + ), + ( + "https://registry.wapm.dev/something/else", + "https://registry.wapm.dev/something/else", + ), + // We don't automatically prepend the domain name with + // "registry", but we will make sure "/" gets turned into "/graphql" + ("https://wapm.dev/", "https://wapm.dev/graphql"), + ("https://wapm.dev", "https://wapm.dev/graphql"), + // local development + ( + "http://localhost:8000/graphql", + "http://localhost:8000/graphql", + ), + ("localhost:8000", "http://localhost:8000/graphql"), + ]; + + for (input, expected) in inputs { + let url = format_graphql(input); + assert_eq!(url, expected); + } + } +} diff --git a/tests/integration/cli/tests/run_unstable.rs b/tests/integration/cli/tests/run_unstable.rs index c4b989989d5..c11495f07a0 100644 --- a/tests/integration/cli/tests/run_unstable.rs +++ b/tests/integration/cli/tests/run_unstable.rs @@ -3,7 +3,7 @@ //! Note that you will need to manually compile the `wasmer` CLI in release mode //! before running any of these tests. use std::{ - io::Read, + io::{ErrorKind, Read}, process::Stdio, time::{Duration, Instant}, }; @@ -15,13 +15,14 @@ use tempfile::TempDir; use wasmer_integration_tests_cli::get_wasmer_path; const RUST_LOG: &str = "info,wasmer_wasi::runners=debug,virtual_fs::trace_fs=trace"; +const HTTP_GET_TIMEOUT: Duration = Duration::from_secs(5); fn wasmer_run_unstable() -> std::process::Command { let mut cmd = std::process::Command::new("cargo"); cmd.arg("run") .arg("--quiet") .arg("--package=wasmer-cli") - .arg("--features=singlepass") + .arg("--features=singlepass,cranelift") .arg("--") .arg("run-unstable"); cmd.env("RUST_LOG", RUST_LOG); @@ -128,7 +129,10 @@ mod webc_on_disk { let mut cmd = wasmer_run_unstable(); cmd.arg(format!("--addr=127.0.0.1:{port}")) .arg(fixtures::static_server()); - let child = JoinableChild::spawn(cmd); + + // Let's run the command and wait until the server has started + let mut child = JoinableChild::spawn(cmd); + child.wait_for_stdout("WCGI Server running"); // make the request let body = http_get(format!("http://127.0.0.1:{port}/")).unwrap(); @@ -162,7 +166,10 @@ mod webc_on_disk { cmd.arg(format!("--addr=127.0.0.1:{port}")) .arg(format!("--mapdir=/path/to:{}", temp.path().display())) .arg(fixtures::static_server()); - let child = JoinableChild::spawn(cmd); + + // Let's run the command and wait until the server has started + let mut child = JoinableChild::spawn(cmd); + child.wait_for_stdout("WCGI Server running"); let body = http_get(format!("http://127.0.0.1:{port}/path/to/file.txt")).unwrap(); assert!(body.contains("Hello, World!"), "{body}"); @@ -371,6 +378,28 @@ impl JoinableChild { JoinableChild(Some(child)) } + /// Keep reading lines from the child's stdout until a line containing the + /// desired text is found. + fn wait_for_stdout(&mut self, text: &str) -> String { + let stderr = self + .0 + .as_mut() + .and_then(|child| child.stdout.as_mut()) + .unwrap(); + + let mut all_output = String::new(); + + loop { + let line = read_line(stderr).unwrap(); + let found = line.contains(text); + all_output.push_str(&line); + + if found { + return all_output; + } + } + } + /// Kill the underlying [`std::process::Child`] and get an [`Assert`] we /// can use to check it. fn join(mut self) -> Assert { @@ -380,6 +409,24 @@ impl JoinableChild { } } +fn read_line(reader: &mut dyn Read) -> Result { + let mut line = Vec::new(); + + while !line.ends_with(&[b'\n']) { + let mut buffer = [0_u8]; + match reader.read_exact(&mut buffer) { + Ok(_) => { + line.push(buffer[0]); + } + Err(e) if e.kind() == ErrorKind::UnexpectedEof => break, + Err(e) => return Err(e), + } + } + + let line = String::from_utf8(line).map_err(|e| std::io::Error::new(ErrorKind::Other, e))?; + Ok(line) +} + impl Drop for JoinableChild { fn drop(&mut self) { if let Some(mut child) = self.0.take() { @@ -395,6 +442,14 @@ impl Drop for JoinableChild { } } + if let Some(mut stdout) = child.stdout.take() { + let mut buffer = String::new(); + if stdout.read_to_string(&mut buffer).is_ok() { + eprintln!("---- STDOUT ----"); + eprintln!("{buffer}"); + } + } + if !std::thread::panicking() { panic!("Child was dropped before being joined"); } @@ -406,12 +461,11 @@ impl Drop for JoinableChild { /// a timeout) if there are any connection errors. fn http_get(url: impl IntoUrl) -> Result { let start = Instant::now(); - let timeout = Duration::from_secs(5); let url = url.into_url().unwrap(); let client = Client::new(); - while start.elapsed() < timeout { + while start.elapsed() < HTTP_GET_TIMEOUT { match client.get(url.clone()).send() { Ok(response) => { return response.error_for_status()?.text();