diff options
author | David Sherret <dsherret@users.noreply.github.com> | 2024-05-06 21:06:01 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-05-07 01:06:01 +0000 |
commit | 1587387bccb6dbecd85f5141dd7543f013d47cd8 (patch) | |
tree | b9829c3e50442deff360cf664555935a0d16a4db /tests/util | |
parent | 87d1ead7d09638172b0e397c8209c759666514da (diff) |
chore(test): move npm registries to separate servers and to the `tests/registry` folder (#23717)
1. Moves the npm registries to their own dedicated ports.
2. Moves the data files out of `tests/testdata/npm/registry` to
`tests/registry/npm`.
Diffstat (limited to 'tests/util')
-rw-r--r-- | tests/util/server/src/lib.rs | 6 | ||||
-rw-r--r-- | tests/util/server/src/npm.rs | 69 | ||||
-rw-r--r-- | tests/util/server/src/servers/hyper_utils.rs | 5 | ||||
-rw-r--r-- | tests/util/server/src/servers/jsr_registry.rs (renamed from tests/util/server/src/servers/registry.rs) | 5 | ||||
-rw-r--r-- | tests/util/server/src/servers/mod.rs | 333 | ||||
-rw-r--r-- | tests/util/server/src/servers/npm_registry.rs | 290 | ||||
-rw-r--r-- | tests/util/server/src/test_server.rs | 13 |
7 files changed, 397 insertions, 324 deletions
diff --git a/tests/util/server/src/lib.rs b/tests/util/server/src/lib.rs index c1046c528..eb881e1b7 100644 --- a/tests/util/server/src/lib.rs +++ b/tests/util/server/src/lib.rs @@ -175,7 +175,7 @@ pub fn deno_config_path() -> PathRef { /// Test server registry url. pub fn npm_registry_url() -> String { - "http://localhost:4545/npm/registry/".to_string() + "http://localhost:4558/".to_string() } pub fn npm_registry_unset_url() -> String { @@ -304,6 +304,8 @@ async fn get_tcp_listener_stream( futures::stream::select_all(listeners) } +pub const TEST_SERVERS_COUNT: usize = 28; + #[derive(Default)] struct HttpServerCount { count: usize, @@ -358,7 +360,7 @@ impl Default for HttpServerStarter { if line.starts_with("ready:") { ready_count += 1; } - if ready_count == 12 { + if ready_count == TEST_SERVERS_COUNT { break; } } else { diff --git a/tests/util/server/src/npm.rs b/tests/util/server/src/npm.rs index 809584ced..7cb6a04f2 100644 --- a/tests/util/server/src/npm.rs +++ b/tests/util/server/src/npm.rs @@ -13,7 +13,8 @@ use once_cell::sync::Lazy; use parking_lot::Mutex; use tar::Builder; -use crate::testdata_path; +use crate::tests_path; +use crate::PathRef; pub const DENOTEST_SCOPE_NAME: &str = "@denotest"; pub const DENOTEST2_SCOPE_NAME: &str = "@denotest2"; @@ -21,8 +22,11 @@ pub const DENOTEST2_SCOPE_NAME: &str = "@denotest2"; pub static PUBLIC_TEST_NPM_REGISTRY: Lazy<TestNpmRegistry> = Lazy::new(|| { TestNpmRegistry::new( NpmRegistryKind::Public, - &format!("http://localhost:{}", crate::servers::PORT), - "/npm/registry", + &format!( + "http://localhost:{}", + crate::servers::PUBLIC_NPM_REGISTRY_PORT + ), + "npm", ) }); @@ -35,8 +39,7 @@ pub static PRIVATE_TEST_NPM_REGISTRY_1: Lazy<TestNpmRegistry> = "http://localhost:{}", crate::servers::PRIVATE_NPM_REGISTRY_1_PORT ), - // TODO: change it - "/npm/registry", + "npm-private", ) }); @@ -51,37 +54,34 @@ struct CustomNpmPackage { } /// Creates tarballs and a registry json file for npm packages -/// in the `testdata/npm/registry/@denotest` directory. +/// in the `tests/registry/npm/@denotest` directory. pub struct TestNpmRegistry { #[allow(unused)] kind: NpmRegistryKind, // Eg. http://localhost:4544/ hostname: String, - // Eg. /registry/npm/ - path: String, + /// Path in the tests/registry folder (Eg. npm) + local_path: String, cache: Mutex<HashMap<String, CustomNpmPackage>>, } impl TestNpmRegistry { - pub fn new(kind: NpmRegistryKind, hostname: &str, path: &str) -> Self { + pub fn new(kind: NpmRegistryKind, hostname: &str, local_path: &str) -> Self { let hostname = hostname.strip_suffix('/').unwrap_or(hostname).to_string(); - assert!( - !path.is_empty(), - "npm test registry must have a non-empty path" - ); - let stripped = path.strip_prefix('/').unwrap_or(path); - let stripped = path.strip_suffix('/').unwrap_or(stripped); - let path = format!("/{}/", stripped); Self { hostname, - path, + local_path: local_path.to_string(), kind, cache: Default::default(), } } + pub fn root_dir(&self) -> PathRef { + tests_path().join("registry").join(&self.local_path) + } + pub fn tarball_bytes( &self, name: &str, @@ -98,6 +98,10 @@ impl TestNpmRegistry { self.get_package_property(name, |p| p.registry_file.as_bytes().to_vec()) } + pub fn package_url(&self, package_name: &str) -> String { + format!("http://{}/{}/", self.hostname, package_name) + } + fn get_package_property<TResult>( &self, package_name: &str, @@ -105,7 +109,7 @@ impl TestNpmRegistry { ) -> Result<Option<TResult>> { // it's ok if multiple threads race here as they will do the same work twice if !self.cache.lock().contains_key(package_name) { - match get_npm_package(&self.hostname, &self.path, package_name)? { + match get_npm_package(&self.hostname, &self.local_path, package_name)? { Some(package) => { self.cache.lock().insert(package_name.to_string(), package); } @@ -115,19 +119,12 @@ impl TestNpmRegistry { Ok(self.cache.lock().get(package_name).map(func)) } - pub fn strip_registry_path_prefix_from_uri_path<'s>( - &self, - uri_path: &'s str, - ) -> Option<&'s str> { - uri_path.strip_prefix(&self.path) - } - pub fn get_test_scope_and_package_name_with_path_from_uri_path<'s>( &self, uri_path: &'s str, ) -> Option<(&'s str, &'s str)> { - let prefix1 = format!("{}{}/", self.path, DENOTEST_SCOPE_NAME); - let prefix2 = format!("{}{}%2f", self.path, DENOTEST_SCOPE_NAME); + let prefix1 = format!("/{}/", DENOTEST_SCOPE_NAME); + let prefix2 = format!("/{}%2f", DENOTEST_SCOPE_NAME); let maybe_package_name_with_path = uri_path .strip_prefix(&prefix1) @@ -137,8 +134,8 @@ impl TestNpmRegistry { return Some((DENOTEST_SCOPE_NAME, package_name_with_path)); } - let prefix1 = format!("{}{}/", self.path, DENOTEST2_SCOPE_NAME); - let prefix2 = format!("{}{}%2f", self.path, DENOTEST2_SCOPE_NAME); + let prefix1 = format!("/{}/", DENOTEST2_SCOPE_NAME); + let prefix2 = format!("/{}%2f", DENOTEST2_SCOPE_NAME); let maybe_package_name_with_path = uri_path .strip_prefix(&prefix1) @@ -150,18 +147,17 @@ impl TestNpmRegistry { None } - - pub fn uri_path_starts_with_registry_path(&self, uri_path: &str) -> bool { - uri_path.starts_with(&self.path) - } } fn get_npm_package( registry_hostname: &str, - registry_path: &str, + local_path: &str, package_name: &str, ) -> Result<Option<CustomNpmPackage>> { - let package_folder = testdata_path().join("npm/registry").join(package_name); + let package_folder = tests_path() + .join("registry") + .join(local_path) + .join(package_name); if !package_folder.exists() { return Ok(None); } @@ -208,8 +204,7 @@ fn get_npm_package( dist.insert("shasum".to_string(), "dummy-value".into()); dist.insert( "tarball".to_string(), - format!("{registry_hostname}{registry_path}{package_name}/{version}.tgz") - .into(), + format!("{registry_hostname}/{package_name}/{version}.tgz").into(), ); tarballs.insert(version.clone(), tarball_bytes); diff --git a/tests/util/server/src/servers/hyper_utils.rs b/tests/util/server/src/servers/hyper_utils.rs index ea15bba0e..c2db7ea66 100644 --- a/tests/util/server/src/servers/hyper_utils.rs +++ b/tests/util/server/src/servers/hyper_utils.rs @@ -31,7 +31,7 @@ pub struct ServerOptions { pub kind: ServerKind, } -type HandlerOutput = +pub type HandlerOutput = Result<Response<UnsyncBoxBody<Bytes, Infallible>>, anyhow::Error>; pub async fn run_server<F, S>(options: ServerOptions, handler: F) @@ -42,6 +42,7 @@ where let fut: Pin<Box<dyn Future<Output = Result<(), anyhow::Error>>>> = async move { let listener = TcpListener::bind(options.addr).await?; + println!("ready: {}", options.addr); loop { let (stream, _) = listener.accept().await?; let io = TokioIo::new(stream); @@ -113,7 +114,7 @@ async fn hyper_serve_connection<I, F, S>( builder .serve_connection(io, service) .await - .map_err(|e| anyhow::anyhow!("{}", e)) + .map_err(|e| anyhow::anyhow!("{:?}", e)) } ServerKind::OnlyHttp1 => { let builder = hyper::server::conn::http1::Builder::new(); diff --git a/tests/util/server/src/servers/registry.rs b/tests/util/server/src/servers/jsr_registry.rs index 09b80c8d5..ce1a34ac9 100644 --- a/tests/util/server/src/servers/registry.rs +++ b/tests/util/server/src/servers/jsr_registry.rs @@ -1,6 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use crate::testdata_path; +use crate::tests_path; use super::run_server; use super::ServerKind; @@ -140,8 +140,7 @@ async fn registry_server_handler( } // serve the registry package files - let mut file_path = - testdata_path().to_path_buf().join("jsr").join("registry"); + let mut file_path = tests_path().join("registry").join("jsr").to_path_buf(); file_path.push( &req.uri().path()[1..] .replace("%2f", "/") diff --git a/tests/util/server/src/servers/mod.rs b/tests/util/server/src/servers/mod.rs index abc451715..ca1932e9d 100644 --- a/tests/util/server/src/servers/mod.rs +++ b/tests/util/server/src/servers/mod.rs @@ -1,4 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + // Usage: provide a port as argument to run hyper_hello benchmark server // otherwise this starts multiple servers on many ports for test endpoints. use base64::prelude::BASE64_STANDARD; @@ -29,10 +30,7 @@ use prost::Message; use std::collections::HashMap; use std::convert::Infallible; use std::env; -use std::net::Ipv6Addr; use std::net::SocketAddr; -use std::net::SocketAddrV6; -use std::path::PathBuf; use std::result::Result; use std::time::Duration; use tokio::io::AsyncWriteExt; @@ -40,7 +38,8 @@ use tokio::net::TcpStream; mod grpc; mod hyper_utils; -mod registry; +mod jsr_registry; +mod npm_registry; mod ws; use hyper_utils::run_server; @@ -48,9 +47,10 @@ use hyper_utils::run_server_with_acceptor; use hyper_utils::ServerKind; use hyper_utils::ServerOptions; +use crate::TEST_SERVERS_COUNT; + use super::https::get_tls_listener_stream; use super::https::SupportedHttpVersions; -use super::npm; use super::std_path; use super::testdata_path; @@ -69,7 +69,10 @@ const REDIRECT_ABSOLUTE_PORT: u16 = 4550; const AUTH_REDIRECT_PORT: u16 = 4551; const TLS_CLIENT_AUTH_PORT: u16 = 4552; const BASIC_AUTH_REDIRECT_PORT: u16 = 4554; +// 4555 is used by the proxy server, and 4556 is used by net_listen_allow_localhost_4555_fail const TLS_PORT: u16 = 4557; +pub(crate) const PUBLIC_NPM_REGISTRY_PORT: u16 = 4558; +pub(crate) const PRIVATE_NPM_REGISTRY_1_PORT: u16 = 4559; const HTTPS_PORT: u16 = 5545; const H1_ONLY_TLS_PORT: u16 = 5546; const H2_ONLY_TLS_PORT: u16 = 5547; @@ -83,9 +86,8 @@ const WS_CLOSE_PORT: u16 = 4244; const WS_PING_PORT: u16 = 4245; const H2_GRPC_PORT: u16 = 4246; const H2S_GRPC_PORT: u16 = 4247; -const REGISTRY_SERVER_PORT: u16 = 4250; +const JSR_REGISTRY_SERVER_PORT: u16 = 4250; const PROVENANCE_MOCK_SERVER_PORT: u16 = 4251; -pub(crate) const PRIVATE_NPM_REGISTRY_1_PORT: u16 = 4252; // Use the single-threaded scheduler. The hyper server is used as a point of // comparison for the (single-threaded!) benchmarks in cli/bench. We're not @@ -120,7 +122,6 @@ pub async fn run_all_servers() { let client_auth_server_https_fut = wrap_client_auth_https_server(HTTPS_CLIENT_AUTH_PORT); let main_server_fut = wrap_main_server(PORT); - let main_server_ipv6_fut = wrap_main_ipv6_server(PORT); let main_server_https_fut = wrap_main_https_server(HTTPS_PORT); let h1_only_server_tls_fut = wrap_https_h1_only_tls_server(H1_ONLY_TLS_PORT); let h2_only_server_tls_fut = wrap_https_h2_only_tls_server(H2_ONLY_TLS_PORT); @@ -128,45 +129,48 @@ pub async fn run_all_servers() { let h2_only_server_fut = wrap_http_h2_only_server(H2_ONLY_PORT); let h2_grpc_server_fut = grpc::h2_grpc_server(H2_GRPC_PORT, H2S_GRPC_PORT); - let registry_server_fut = registry::registry_server(REGISTRY_SERVER_PORT); + let registry_server_fut = + jsr_registry::registry_server(JSR_REGISTRY_SERVER_PORT); let provenance_mock_server_fut = - registry::provenance_mock_server(PROVENANCE_MOCK_SERVER_PORT); - let private_npm_registry_1_server_fut = - wrap_private_npm_registry1(PRIVATE_NPM_REGISTRY_1_PORT); - - let server_fut = async { - futures::join!( - redirect_server_fut, - ws_server_fut, - ws_ping_server_fut, - wss_server_fut, - wss2_server_fut, - tls_server_fut, - tls_client_auth_server_fut, - ws_close_server_fut, - another_redirect_server_fut, - auth_redirect_server_fut, - basic_auth_redirect_server_fut, - inf_redirects_server_fut, - double_redirects_server_fut, - abs_redirect_server_fut, - main_server_fut, - main_server_ipv6_fut, - main_server_https_fut, - client_auth_server_https_fut, - h1_only_server_tls_fut, - h2_only_server_tls_fut, - h1_only_server_fut, - h2_only_server_fut, - h2_grpc_server_fut, - registry_server_fut, - provenance_mock_server_fut, - private_npm_registry_1_server_fut, - ) - } - .boxed_local(); - - server_fut.await; + jsr_registry::provenance_mock_server(PROVENANCE_MOCK_SERVER_PORT); + + let npm_registry_server_futs = + npm_registry::public_npm_registry(PUBLIC_NPM_REGISTRY_PORT); + let private_npm_registry_1_server_futs = + npm_registry::private_npm_registry1(PRIVATE_NPM_REGISTRY_1_PORT); + + let mut futures = vec![ + redirect_server_fut.boxed_local(), + ws_server_fut.boxed_local(), + ws_ping_server_fut.boxed_local(), + wss_server_fut.boxed_local(), + wss2_server_fut.boxed_local(), + tls_server_fut.boxed_local(), + tls_client_auth_server_fut.boxed_local(), + ws_close_server_fut.boxed_local(), + another_redirect_server_fut.boxed_local(), + auth_redirect_server_fut.boxed_local(), + basic_auth_redirect_server_fut.boxed_local(), + inf_redirects_server_fut.boxed_local(), + double_redirects_server_fut.boxed_local(), + abs_redirect_server_fut.boxed_local(), + main_server_fut.boxed_local(), + main_server_https_fut.boxed_local(), + client_auth_server_https_fut.boxed_local(), + h1_only_server_tls_fut.boxed_local(), + h2_only_server_tls_fut.boxed_local(), + h1_only_server_fut.boxed_local(), + h2_only_server_fut.boxed_local(), + h2_grpc_server_fut.boxed_local(), + registry_server_fut.boxed_local(), + provenance_mock_server_fut.boxed_local(), + ]; + futures.extend(npm_registry_server_futs); + futures.extend(private_npm_registry_1_server_futs); + + assert_eq!(futures.len(), TEST_SERVERS_COUNT); + + futures::future::join_all(futures).await; } fn empty_body() -> UnsyncBoxBody<Bytes, Infallible> { @@ -1131,16 +1135,7 @@ async fn main_server( return Ok(file_resp); } - // serve npm registry files - if let Some(resp) = try_serve_npm_registry( - uri_path, - file_path.clone(), - &npm::PUBLIC_TEST_NPM_REGISTRY, - ) - .await - { - return resp; - } else if let Some(suffix) = uri_path.strip_prefix("/deno_std/") { + if let Some(suffix) = uri_path.strip_prefix("/deno_std/") { let file_path = std_path().join(suffix); if let Ok(file) = tokio::fs::read(&file_path).await { let file_resp = custom_headers(uri_path, file); @@ -1164,216 +1159,6 @@ async fn main_server( }; } -const PRIVATE_NPM_REGISTRY_AUTH_TOKEN: &str = "private-reg-token"; - -async fn wrap_private_npm_registry1(port: u16) { - let npm_registry_addr = SocketAddr::from(([127, 0, 0, 1], port)); - run_server( - ServerOptions { - addr: npm_registry_addr, - kind: ServerKind::Auto, - error_msg: "HTTP server error", - }, - private_npm_registry1, - ) - .await; -} - -async fn private_npm_registry1( - req: Request<hyper::body::Incoming>, -) -> Result<Response<UnsyncBoxBody<Bytes, Infallible>>, anyhow::Error> { - let auth = req - .headers() - .get("authorization") - .and_then(|x| x.to_str().ok()) - .unwrap_or_default(); - if auth != format!("Bearer {}", PRIVATE_NPM_REGISTRY_AUTH_TOKEN) { - return Ok( - Response::builder() - .status(StatusCode::UNAUTHORIZED) - .body(empty_body()) - .unwrap(), - ); - } - - let uri_path = req.uri().path(); - let mut testdata_file_path = testdata_path().to_path_buf(); - testdata_file_path.push(&uri_path[1..].replace("%2f", "/")); - - if let Some(resp) = try_serve_npm_registry( - uri_path, - testdata_file_path, - &npm::PRIVATE_TEST_NPM_REGISTRY_1, - ) - .await - { - return resp; - } - - Response::builder() - .status(StatusCode::NOT_FOUND) - .body(empty_body()) - .map_err(|e| e.into()) -} - -fn handle_custom_npm_registry_path( - scope_name: &str, - path: &str, - test_npm_registry: &npm::TestNpmRegistry, -) -> Result<Option<Response<UnsyncBoxBody<Bytes, Infallible>>>, anyhow::Error> { - let mut parts = path - .split('/') - .filter(|p| !p.is_empty()) - .collect::<Vec<_>>(); - let remainder = parts.split_off(1); - let name = parts[0]; - let package_name = format!("{}/{}", scope_name, name); - - if remainder.len() == 1 { - if let Some(file_bytes) = test_npm_registry - .tarball_bytes(&package_name, remainder[0].trim_end_matches(".tgz"))? - { - let file_resp = custom_headers("file.tgz", file_bytes); - return Ok(Some(file_resp)); - } - } else if remainder.is_empty() { - if let Some(registry_file) = - test_npm_registry.registry_file(&package_name)? - { - let file_resp = custom_headers("registry.json", registry_file); - return Ok(Some(file_resp)); - } - } - - Ok(None) -} - -fn should_download_npm_packages() -> bool { - // when this env var is set, it will download and save npm packages - // to the testdata/npm/registry directory - std::env::var("DENO_TEST_UTIL_UPDATE_NPM") == Ok("1".to_string()) -} - -async fn try_serve_npm_registry( - uri_path: &str, - mut testdata_file_path: PathBuf, - test_npm_registry: &npm::TestNpmRegistry, -) -> Option<Result<Response<UnsyncBoxBody<Bytes, Infallible>>, anyhow::Error>> { - if let Some((scope_name, package_name_with_path)) = test_npm_registry - .get_test_scope_and_package_name_with_path_from_uri_path(uri_path) - { - // serve all requests to the `DENOTEST_SCOPE_NAME` or `DENOTEST2_SCOPE_NAME` - // using the file system at that path - match handle_custom_npm_registry_path( - scope_name, - package_name_with_path, - test_npm_registry, - ) { - Ok(Some(response)) => return Some(Ok(response)), - Ok(None) => {} // ignore, not found - Err(err) => { - return Some( - Response::builder() - .status(StatusCode::INTERNAL_SERVER_ERROR) - .body(string_body(&format!("{err:#}"))) - .map_err(|e| e.into()), - ); - } - } - } else if test_npm_registry.uri_path_starts_with_registry_path(uri_path) { - // otherwise, serve based on registry.json and tgz files - let is_tarball = uri_path.ends_with(".tgz"); - if !is_tarball { - testdata_file_path.push("registry.json"); - } - if let Ok(file) = tokio::fs::read(&testdata_file_path).await { - let file_resp = custom_headers(uri_path, file); - return Some(Ok(file_resp)); - } else if should_download_npm_packages() { - if let Err(err) = download_npm_registry_file( - test_npm_registry, - uri_path, - &testdata_file_path, - is_tarball, - ) - .await - { - return Some( - Response::builder() - .status(StatusCode::INTERNAL_SERVER_ERROR) - .body(string_body(&format!("{err:#}"))) - .map_err(|e| e.into()), - ); - }; - - // serve the file - if let Ok(file) = tokio::fs::read(&testdata_file_path).await { - let file_resp = custom_headers(uri_path, file); - return Some(Ok(file_resp)); - } - } - } - - None -} - -// Replaces URL of public npm registry (`https://registry.npmjs.org/`) with -// the test registry (`http://localhost:4545/npm/registry/`). -// -// These strings end up in `registry.json` files for each downloaded package -// that are stored in `tests/testdata/` directory. -// -// If another npm test registry wants to use them, it should replace -// these values with appropriate URL when serving. -fn replace_default_npm_registry_url_with_test_npm_registry_url( - str_: String, - package_name: &str, -) -> String { - str_.replace( - &format!("https://registry.npmjs.org/{package_name}/-/"), - &format!("http://localhost:4545/npm/registry/{package_name}/"), - ) -} - -async fn download_npm_registry_file( - test_npm_registry: &npm::TestNpmRegistry, - uri_path: &str, - testdata_file_path: &PathBuf, - is_tarball: bool, -) -> Result<(), anyhow::Error> { - let url_parts = test_npm_registry - .strip_registry_path_prefix_from_uri_path(uri_path) - .unwrap() - .split('/') - .collect::<Vec<_>>(); - let package_name = if url_parts[0].starts_with('@') { - url_parts.into_iter().take(2).collect::<Vec<_>>().join("/") - } else { - url_parts.into_iter().take(1).collect::<Vec<_>>().join("/") - }; - let url = if is_tarball { - let file_name = testdata_file_path.file_name().unwrap().to_string_lossy(); - format!("https://registry.npmjs.org/{package_name}/-/{file_name}") - } else { - format!("https://registry.npmjs.org/{package_name}") - }; - let client = reqwest::Client::new(); - let response = client.get(url).send().await?; - let bytes = response.bytes().await?; - let bytes = if is_tarball { - bytes.to_vec() - } else { - replace_default_npm_registry_url_with_test_npm_registry_url( - String::from_utf8(bytes.to_vec()).unwrap(), - &package_name, - ) - .into_bytes() - }; - std::fs::create_dir_all(testdata_file_path.parent().unwrap())?; - std::fs::write(testdata_file_path, bytes)?; - Ok(()) -} - async fn wrap_redirect_server(port: u16) { let redirect_addr = SocketAddr::from(([127, 0, 0, 1], port)); run_server( @@ -1467,21 +1252,9 @@ async fn wrap_abs_redirect_server(port: u16) { async fn wrap_main_server(port: u16) { let main_server_addr = SocketAddr::from(([127, 0, 0, 1], port)); - wrap_main_server_for_addr(&main_server_addr).await -} - -// necessary because on Windows the npm binary will resolve localhost to ::1 -async fn wrap_main_ipv6_server(port: u16) { - let ipv6_loopback = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1); - let main_server_addr = - SocketAddr::V6(SocketAddrV6::new(ipv6_loopback, port, 0, 0)); - wrap_main_server_for_addr(&main_server_addr).await -} - -async fn wrap_main_server_for_addr(main_server_addr: &SocketAddr) { run_server( ServerOptions { - addr: *main_server_addr, + addr: main_server_addr, kind: ServerKind::Auto, error_msg: "HTTP server error", }, @@ -1588,7 +1361,7 @@ async fn wrap_client_auth_https_server(port: u16) { .await } -fn custom_headers( +pub fn custom_headers( p: &str, body: Vec<u8>, ) -> Response<UnsyncBoxBody<Bytes, Infallible>> { diff --git a/tests/util/server/src/servers/npm_registry.rs b/tests/util/server/src/servers/npm_registry.rs new file mode 100644 index 000000000..909b9e203 --- /dev/null +++ b/tests/util/server/src/servers/npm_registry.rs @@ -0,0 +1,290 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +use crate::npm; + +use super::custom_headers; +use super::empty_body; +use super::hyper_utils::HandlerOutput; +use super::run_server; +use super::string_body; +use super::ServerKind; +use super::ServerOptions; +use bytes::Bytes; +use futures::future::LocalBoxFuture; +use futures::Future; +use futures::FutureExt; +use http_body_util::combinators::UnsyncBoxBody; +use hyper::body::Incoming; +use hyper::Request; +use hyper::Response; +use hyper::StatusCode; +use std::convert::Infallible; +use std::net::Ipv6Addr; +use std::net::SocketAddr; +use std::net::SocketAddrV6; +use std::path::PathBuf; + +pub fn public_npm_registry(port: u16) -> Vec<LocalBoxFuture<'static, ()>> { + run_npm_server(port, "npm registry server error", { + move |req| async move { + handle_req_for_registry(req, &npm::PUBLIC_TEST_NPM_REGISTRY).await + } + }) +} + +const PRIVATE_NPM_REGISTRY_AUTH_TOKEN: &str = "private-reg-token"; + +pub fn private_npm_registry1(port: u16) -> Vec<LocalBoxFuture<'static, ()>> { + run_npm_server( + port, + "npm private registry server error", + private_npm_registry1_handler, + ) +} + +fn run_npm_server<F, S>( + port: u16, + error_msg: &'static str, + handler: F, +) -> Vec<LocalBoxFuture<()>> +where + F: Fn(Request<hyper::body::Incoming>) -> S + Copy + 'static, + S: Future<Output = HandlerOutput> + 'static, +{ + let npm_registry_addr = SocketAddr::from(([127, 0, 0, 1], port)); + let ipv6_loopback = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1); + let npm_registry_ipv6_addr = + SocketAddr::V6(SocketAddrV6::new(ipv6_loopback, port, 0, 0)); + vec![ + run_npm_server_for_addr(npm_registry_addr, error_msg, handler) + .boxed_local(), + // necessary because the npm binary will sometimes resolve localhost to ::1 + run_npm_server_for_addr(npm_registry_ipv6_addr, error_msg, handler) + .boxed_local(), + ] +} + +async fn run_npm_server_for_addr<F, S>( + addr: SocketAddr, + error_msg: &'static str, + handler: F, +) where + F: Fn(Request<hyper::body::Incoming>) -> S + Copy + 'static, + S: Future<Output = HandlerOutput> + 'static, +{ + run_server( + ServerOptions { + addr, + kind: ServerKind::Auto, + error_msg, + }, + handler, + ) + .await +} + +async fn private_npm_registry1_handler( + req: Request<hyper::body::Incoming>, +) -> Result<Response<UnsyncBoxBody<Bytes, Infallible>>, anyhow::Error> { + let auth = req + .headers() + .get("authorization") + .and_then(|x| x.to_str().ok()) + .unwrap_or_default(); + if auth != format!("Bearer {}", PRIVATE_NPM_REGISTRY_AUTH_TOKEN) { + return Ok( + Response::builder() + .status(StatusCode::UNAUTHORIZED) + .body(empty_body()) + .unwrap(), + ); + } + + handle_req_for_registry(req, &npm::PRIVATE_TEST_NPM_REGISTRY_1).await +} + +async fn handle_req_for_registry( + req: Request<Incoming>, + test_npm_registry: &npm::TestNpmRegistry, +) -> Result<Response<UnsyncBoxBody<Bytes, Infallible>>, anyhow::Error> { + let root_dir = test_npm_registry.root_dir(); + + // serve the registry package files + let uri_path = req.uri().path(); + let mut file_path = root_dir.to_path_buf(); + file_path.push(&uri_path[1..].replace("%2f", "/").replace("%2F", "/")); + + // serve if the filepath exists + if let Ok(file) = tokio::fs::read(&file_path).await { + let file_resp = custom_headers(uri_path, file); + return Ok(file_resp); + } + + // otherwise try to serve from the registry + if let Some(resp) = + try_serve_npm_registry(uri_path, file_path.clone(), test_npm_registry).await + { + return resp; + } + + Response::builder() + .status(StatusCode::NOT_FOUND) + .body(empty_body()) + .map_err(|e| e.into()) +} + +fn handle_custom_npm_registry_path( + scope_name: &str, + path: &str, + test_npm_registry: &npm::TestNpmRegistry, +) -> Result<Option<Response<UnsyncBoxBody<Bytes, Infallible>>>, anyhow::Error> { + let mut parts = path + .split('/') + .filter(|p| !p.is_empty()) + .collect::<Vec<_>>(); + let remainder = parts.split_off(1); + let name = parts[0]; + let package_name = format!("{}/{}", scope_name, name); + + if remainder.len() == 1 { + if let Some(file_bytes) = test_npm_registry + .tarball_bytes(&package_name, remainder[0].trim_end_matches(".tgz"))? + { + let file_resp = custom_headers("file.tgz", file_bytes); + return Ok(Some(file_resp)); + } + } else if remainder.is_empty() { + if let Some(registry_file) = + test_npm_registry.registry_file(&package_name)? + { + let file_resp = custom_headers("registry.json", registry_file); + return Ok(Some(file_resp)); + } + } + + Ok(None) +} + +fn should_download_npm_packages() -> bool { + // when this env var is set, it will download and save npm packages + // to the tests/registry/npm directory + std::env::var("DENO_TEST_UTIL_UPDATE_NPM") == Ok("1".to_string()) +} + +async fn try_serve_npm_registry( + uri_path: &str, + mut testdata_file_path: PathBuf, + test_npm_registry: &npm::TestNpmRegistry, +) -> Option<Result<Response<UnsyncBoxBody<Bytes, Infallible>>, anyhow::Error>> { + if let Some((scope_name, package_name_with_path)) = test_npm_registry + .get_test_scope_and_package_name_with_path_from_uri_path(uri_path) + { + // serve all requests to the `DENOTEST_SCOPE_NAME` or `DENOTEST2_SCOPE_NAME` + // using the file system at that path + match handle_custom_npm_registry_path( + scope_name, + package_name_with_path, + test_npm_registry, + ) { + Ok(Some(response)) => return Some(Ok(response)), + Ok(None) => {} // ignore, not found + Err(err) => { + return Some( + Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(string_body(&format!("{err:#}"))) + .map_err(|e| e.into()), + ); + } + } + } else { + // otherwise, serve based on registry.json and tgz files + let is_tarball = uri_path.ends_with(".tgz"); + if !is_tarball { + testdata_file_path.push("registry.json"); + } + if let Ok(file) = tokio::fs::read(&testdata_file_path).await { + let file_resp = custom_headers(uri_path, file); + return Some(Ok(file_resp)); + } else if should_download_npm_packages() { + if let Err(err) = download_npm_registry_file( + test_npm_registry, + uri_path, + &testdata_file_path, + is_tarball, + ) + .await + { + return Some( + Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(string_body(&format!("{err:#}"))) + .map_err(|e| e.into()), + ); + }; + + // serve the file + if let Ok(file) = tokio::fs::read(&testdata_file_path).await { + let file_resp = custom_headers(uri_path, file); + return Some(Ok(file_resp)); + } + } + } + + None +} + +// Replaces URL of public npm registry (`https://registry.npmjs.org/`) with +// the test registry (`http://localhost:4558`). +// +// These strings end up in `registry.json` files for each downloaded package +// that are stored in `tests/testdata/` directory. +// +// If another npm test registry wants to use them, it should replace +// these values with appropriate URL when serving. +fn replace_default_npm_registry_url_with_test_npm_registry_url( + text: String, + npm_registry: &npm::TestNpmRegistry, + package_name: &str, +) -> String { + text.replace( + &format!("https://registry.npmjs.org/{}/-/", package_name), + &npm_registry.package_url(package_name), + ) +} + +async fn download_npm_registry_file( + test_npm_registry: &npm::TestNpmRegistry, + uri_path: &str, + testdata_file_path: &PathBuf, + is_tarball: bool, +) -> Result<(), anyhow::Error> { + let url_parts = uri_path.split('/').collect::<Vec<_>>(); + let package_name = if url_parts[0].starts_with('@') { + url_parts.into_iter().take(2).collect::<Vec<_>>().join("/") + } else { + url_parts.into_iter().take(1).collect::<Vec<_>>().join("/") + }; + let url = if is_tarball { + let file_name = testdata_file_path.file_name().unwrap().to_string_lossy(); + format!("https://registry.npmjs.org/{package_name}/-/{file_name}") + } else { + format!("https://registry.npmjs.org/{package_name}") + }; + let client = reqwest::Client::new(); + let response = client.get(url).send().await?; + let bytes = response.bytes().await?; + let bytes = if is_tarball { + bytes.to_vec() + } else { + replace_default_npm_registry_url_with_test_npm_registry_url( + String::from_utf8(bytes.to_vec()).unwrap(), + test_npm_registry, + &package_name, + ) + .into_bytes() + }; + std::fs::create_dir_all(testdata_file_path.parent().unwrap())?; + std::fs::write(testdata_file_path, bytes)?; + Ok(()) +} diff --git a/tests/util/server/src/test_server.rs b/tests/util/server/src/test_server.rs index b0f74d606..19e33f9f5 100644 --- a/tests/util/server/src/test_server.rs +++ b/tests/util/server/src/test_server.rs @@ -1,5 +1,18 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. fn main() { + setup_panic_hook(); test_server::servers::run_all_servers(); } + +fn setup_panic_hook() { + // Tokio does not exit the process when a task panics, so we define a custom + // panic hook to implement this behaviour. + let orig_hook = std::panic::take_hook(); + std::panic::set_hook(Box::new(move |panic_info| { + eprintln!("\n============================================================"); + eprintln!("Test server panicked!\n"); + orig_hook(panic_info); + std::process::exit(1); + })); +} |