summaryrefslogtreecommitdiff
path: root/tests/util
diff options
context:
space:
mode:
authorDavid Sherret <dsherret@users.noreply.github.com>2024-05-06 21:06:01 -0400
committerGitHub <noreply@github.com>2024-05-07 01:06:01 +0000
commit1587387bccb6dbecd85f5141dd7543f013d47cd8 (patch)
treeb9829c3e50442deff360cf664555935a0d16a4db /tests/util
parent87d1ead7d09638172b0e397c8209c759666514da (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.rs6
-rw-r--r--tests/util/server/src/npm.rs69
-rw-r--r--tests/util/server/src/servers/hyper_utils.rs5
-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.rs333
-rw-r--r--tests/util/server/src/servers/npm_registry.rs290
-rw-r--r--tests/util/server/src/test_server.rs13
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);
+ }));
+}