Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleaned up the GraphQL endpoint logic and added tests #3837

Merged
merged 3 commits into from
May 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
148 changes: 101 additions & 47 deletions lib/registry/src/config.rs
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -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) {
Michael-F-Bryan marked this conversation as resolved.
Show resolved Hide resolved
// 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");
Michael-F-Bryan marked this conversation as resolved.
Show resolved Hide resolved
}

format!("https://registry.{domain_name}/graphql")
}

fn test_if_registry_present(registry: &str) -> Result<(), String> {
Expand All @@ -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(&registries.get_current_registry()),
None
);
registries.set_current_registry("https://registry.wapm.io");
assert_eq!(
registries.get_login_token_for_registry(&registries.get_current_registry()),
Some("token1".to_string())
);
registries.clear_current_registry_token();
assert_eq!(
registries.get_login_token_for_registry(&registries.get_current_registry()),
None
);
}

impl MultiRegistry {
/// Gets the current (active) registry URL
pub fn clear_current_registry_token(&mut self) {
Expand Down Expand Up @@ -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(&registries.get_current_registry()),
None
);
registries.set_current_registry("https://registry.wapm.io");
assert_eq!(
registries.get_login_token_for_registry(&registries.get_current_registry()),
Some("token1".to_string())
);
registries.clear_current_registry_token();
assert_eq!(
registries.get_login_token_for_registry(&registries.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);
}
}
}
66 changes: 60 additions & 6 deletions tests/integration/cli/tests/run_unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
};
Expand All @@ -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);
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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}");
Expand Down Expand Up @@ -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 {
Expand All @@ -380,6 +409,24 @@ impl JoinableChild {
}
}

fn read_line(reader: &mut dyn Read) -> Result<String, std::io::Error> {
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() {
Expand All @@ -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");
}
Expand All @@ -406,12 +461,11 @@ impl Drop for JoinableChild {
/// a timeout) if there are any connection errors.
fn http_get(url: impl IntoUrl) -> Result<String, reqwest::Error> {
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();
Expand Down