use std::{
collections::HashMap,
net::{Ipv4Addr, SocketAddrV4},
path::{Path, PathBuf},
};
use oci_client::client::ClientConfig;
use testcontainers::{
ContainerAsync, GenericImage, ImageExt,
core::{IntoContainerPort, WaitFor},
runners::AsyncRunner,
};
use tokio::{net::TcpListener, process::Command};
use wasm_pkg_client::{Config, CustomConfig, Registry, RegistryMetadata, oci::OciRegistryConfig};
use wasm_pkg_core::wit::WIT_DEPS_DIR;
pub async fn find_open_port() -> u16 {
TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0))
.await
.expect("failed to bind random port")
.local_addr()
.map(|addr| addr.port())
.expect("failed to get local address from opened TCP socket")
}
pub async fn start_registry() -> (Config, Registry, ContainerAsync<GenericImage>) {
let port = find_open_port().await;
let container = GenericImage::new("registry", "2")
.with_wait_for(WaitFor::message_on_stderr("listening on [::]:5000"))
.with_mapped_port(port, 5000.tcp())
.start()
.await
.expect("Failed to start test container");
let registry: Registry = format!("localhost:{}", port).parse().unwrap();
let mut config = Config::empty();
config.set_namespace_registry(
"wasi".parse().unwrap(),
wasm_pkg_client::RegistryMapping::Registry("wasi.dev".parse().unwrap()),
);
let reg_conf = config.get_or_insert_registry_config_mut(®istry);
reg_conf
.set_backend_config(
"oci",
OciRegistryConfig {
client_config: ClientConfig {
protocol: oci_client::client::ClientProtocol::Http,
..Default::default()
},
credentials: None,
},
)
.unwrap();
(config, registry, container)
}
pub const TRANSITIVE_LOCAL_NAMESPACES: &[&str] =
&["example-a", "example-b", "example-c", "example-d"];
pub fn map_transitive_local_namespaces(config: &Config, registry: &Registry) -> Config {
let mut mapped = config.clone();
for ns in TRANSITIVE_LOCAL_NAMESPACES {
mapped = map_namespace(&mapped, ns, registry);
}
mapped
}
pub async fn publish_transitive_local(config: &Config) -> Fixture {
let fixture = load_fixture_from(transitive_local_fixture()).await;
let status = fixture
.command_with_config(config)
.await
.args(["publish", "--workspace"])
.status()
.await
.expect("spawn wkg publish");
assert!(status.success(), "`wkg publish --workspace` should succeed",);
fixture
}
pub fn map_namespace(config: &Config, namespace: &str, registry: &Registry) -> Config {
let mut config = config.clone();
let mut metadata = RegistryMetadata::default();
metadata.preferred_protocol = Some("oci".to_string());
let mut meta = serde_json::Map::new();
meta.insert("registry".to_string(), registry.to_string().into());
metadata.protocol_configs = HashMap::from_iter([("oci".to_string(), meta)]);
config.set_namespace_registry(
namespace.parse().unwrap(),
wasm_pkg_client::RegistryMapping::Custom(CustomConfig {
registry: registry.to_owned(),
metadata,
}),
);
config
}
pub struct Fixture {
pub temp_dir: tempfile::TempDir,
pub fixture_path: PathBuf,
}
impl Fixture {
pub fn command(&self) -> Command {
let mut cmd = Command::new(env!("CARGO_BIN_EXE_wkg"));
cmd.current_dir(&self.fixture_path);
cmd.env("WKG_CACHE_DIR", self.temp_dir.path().join("cache"));
cmd
}
pub async fn command_with_config(&self, config: &Config) -> Command {
let config_path = self.temp_dir.path().join("config.toml");
config
.to_file(&config_path)
.await
.expect("failed to write config");
let mut cmd = self.command();
cmd.env("WKG_CONFIG_FILE", config_path);
cmd
}
}
pub fn fixture_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("fixtures")
}
pub fn transitive_local_fixture() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../wasm-pkg-core/tests/fixtures/transitive-local")
}
pub async fn load_fixture(fixture: &str) -> Fixture {
load_fixture_from(fixture_dir().join(fixture)).await
}
pub async fn load_fixture_from(src: impl AsRef<Path>) -> Fixture {
let src = src.as_ref();
let temp_dir = tempfile::tempdir().expect("Failed to create tempdir");
tokio::fs::metadata(src)
.await
.expect("Fixture does not exist or couldn't be read");
let copied_path = temp_dir.path().join(src.file_name().unwrap());
copy_dir(src, &copied_path)
.await
.expect("Failed to copy fixture");
Fixture {
temp_dir,
fixture_path: copied_path,
}
}
pub async fn copy_dir(
source: impl AsRef<Path>,
destination: impl AsRef<Path>,
) -> anyhow::Result<()> {
tokio::fs::create_dir_all(&destination).await?;
let mut entries = tokio::fs::read_dir(source).await?;
while let Some(entry) = entries.next_entry().await? {
let filetype = entry.file_type().await?;
if filetype.is_dir() {
if entry.path().file_name().unwrap_or_default() == WIT_DEPS_DIR {
continue;
}
Box::pin(copy_dir(
entry.path(),
destination.as_ref().join(entry.file_name()),
))
.await?;
} else {
let path = entry.path();
let extension = path.extension().unwrap_or_default();
if extension == "lock" {
continue;
}
tokio::fs::copy(path, destination.as_ref().join(entry.file_name())).await?;
}
}
Ok(())
}