use std::{
collections::HashMap,
net::{Ipv4Addr, SocketAddrV4},
path::{Path, PathBuf},
};
use oci_client::client::ClientConfig;
use testcontainers::{
core::{IntoContainerPort, WaitFor},
runners::AsyncRunner,
ContainerAsync, GenericImage, ImageExt,
};
use tokio::{net::TcpListener, process::Command};
use wasm_pkg_client::{oci::OciRegistryConfig, Config, CustomConfig, Registry, RegistryMetadata};
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 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 async fn load_fixture(fixture: &str) -> Fixture {
let temp_dir = tempfile::tempdir().expect("Failed to create tempdir");
let fixture_path = fixture_dir().join(fixture);
tokio::fs::metadata(&fixture_path)
.await
.expect("Fixture does not exist or couldn't be read");
let copied_path = temp_dir.path().join(fixture_path.file_name().unwrap());
copy_dir(&fixture_path, &copied_path)
.await
.expect("Failed to copy fixture");
Fixture {
temp_dir,
fixture_path: copied_path,
}
}
#[allow(dead_code)]
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() == "deps" {
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(())
}