From 679bc2179a23549159f481620e2ebbcc28fae9f8 Mon Sep 17 00:00:00 2001 From: Trevor Elliott Date: Thu, 13 Jun 2024 10:52:34 -0700 Subject: [PATCH] Enable more tests as components --- cli/tests/integration/async_io.rs | 11 ++- cli/tests/integration/body.rs | 17 ++-- cli/tests/integration/client_certs.rs | 22 ++--- cli/tests/integration/common.rs | 23 +++++ cli/tests/integration/config_store_lookup.rs | 24 ++--- .../integration/device_detection_lookup.rs | 17 ++-- cli/tests/integration/dictionary_lookup.rs | 23 ++--- cli/tests/integration/downstream_req.rs | 11 ++- cli/tests/integration/edge_rate_limiting.rs | 11 ++- cli/tests/integration/geolocation_lookup.rs | 50 ++++++----- cli/tests/integration/grpc.rs | 12 +-- cli/tests/integration/http_semantics.rs | 17 ++-- cli/tests/integration/kv_store.rs | 84 ++++++++++-------- cli/tests/integration/logging.rs | 11 ++- cli/tests/integration/secret_store.rs | 11 ++- cli/tests/integration/sending_response.rs | 73 ++++++++------- cli/tests/integration/sleep.rs | 24 +++-- cli/tests/integration/upstream.rs | 25 +++--- cli/tests/integration/upstream_async.rs | 11 ++- cli/tests/integration/upstream_dynamic.rs | 23 ++--- cli/tests/integration/upstream_streaming.rs | 13 +-- crates/adapter/src/fastly/core.rs | 68 +++++++++----- lib/data/viceroy-component-adapter.wasm | Bin 179589 -> 177678 bytes lib/src/component/config_store.rs | 8 +- lib/src/component/device_detection.rs | 25 ++++++ lib/src/component/dictionary.rs | 8 +- lib/src/component/erl.rs | 60 +++++++++++++ lib/src/component/http_req.rs | 63 +++++++++---- lib/src/component/mod.rs | 4 + lib/wit/deps/fastly/compute.wit | 2 +- 30 files changed, 493 insertions(+), 258 deletions(-) create mode 100644 lib/src/component/device_detection.rs create mode 100644 lib/src/component/erl.rs diff --git a/cli/tests/integration/async_io.rs b/cli/tests/integration/async_io.rs index fda48333..7a182f3c 100644 --- a/cli/tests/integration/async_io.rs +++ b/cli/tests/integration/async_io.rs @@ -1,4 +1,7 @@ -use crate::common::{Test, TestResult}; +use crate::{ + common::{Test, TestResult}, + viceroy_test_sequential, +}; use hyper::{body::HttpBody, Body, Request, Response, StatusCode}; use std::sync::{ atomic::{AtomicUsize, Ordering}, @@ -13,8 +16,7 @@ use tokio::sync::Barrier; // // https://github.com/fastly/Viceroy/issues/207 tracks the broader issue. #[cfg(target_family = "unix")] -#[tokio::test(flavor = "multi_thread")] -async fn async_io_methods() -> TestResult { +viceroy_test_sequential!(async_io_methods, |is_component| { let request_count = Arc::new(AtomicUsize::new(0)); let req_count_1 = request_count.clone(); let req_count_2 = request_count.clone(); @@ -34,6 +36,7 @@ async fn async_io_methods() -> TestResult { // total and will behave differently depending on which request # it is // processing. let test = Test::using_fixture("async_io.wasm") + .adapt_component(is_component) .async_backend("Simple", "/", None, move |req: Request| { assert_eq!(req.headers()["Host"], "simple.org"); let req_count_1 = req_count_1.clone(); @@ -206,4 +209,4 @@ async fn async_io_methods() -> TestResult { assert_eq!(resp.headers()["Ready-Index"], "timeout"); Ok(()) -} +}); diff --git a/cli/tests/integration/body.rs b/cli/tests/integration/body.rs index b86f483d..8a205de0 100644 --- a/cli/tests/integration/body.rs +++ b/cli/tests/integration/body.rs @@ -1,13 +1,16 @@ //! Tests related to HTTP request and response bodies. use { - crate::common::{Test, TestResult}, + crate::{ + common::{Test, TestResult}, + viceroy_test, + }, hyper::{body, StatusCode}, }; -#[tokio::test(flavor = "multi_thread")] -async fn bodies_can_be_written_and_appended() -> TestResult { +viceroy_test!(bodies_can_be_written_and_appended, |is_component| { let resp = Test::using_fixture("write-body.wasm") + .adapt_component(is_component) .against_empty() .await?; @@ -19,13 +22,13 @@ async fn bodies_can_be_written_and_appended() -> TestResult { assert_eq!(&body, "Hello, Viceroy!"); Ok(()) -} +}); -#[tokio::test(flavor = "multi_thread")] -async fn bodies_can_be_written_and_read() -> TestResult { +viceroy_test!(bodies_can_be_written_and_read, |is_component| { let resp = Test::using_fixture("write-and-read-body.wasm") + .adapt_component(is_component) .against_empty() .await?; assert_eq!(resp.status(), StatusCode::OK); Ok(()) -} +}); diff --git a/cli/tests/integration/client_certs.rs b/cli/tests/integration/client_certs.rs index a13d0f1a..03ad733f 100644 --- a/cli/tests/integration/client_certs.rs +++ b/cli/tests/integration/client_certs.rs @@ -1,4 +1,7 @@ -use crate::common::{Test, TestResult}; +use crate::{ + common::{Test, TestResult}, + viceroy_test, +}; use base64::engine::{general_purpose, Engine}; use hyper::http::response; use hyper::server::conn::AddrIncoming; @@ -127,10 +130,8 @@ fn build_server_tls_config() -> ServerConfig { .expect("valid server cert") } -#[tokio::test(flavor = "multi_thread")] -#[serial_test::serial] -async fn custom_ca_works() -> TestResult { - let test = Test::using_fixture("mutual-tls.wasm"); +viceroy_test!(custom_ca_works, |is_component| { + let test = Test::using_fixture("mutual-tls.wasm").adapt_component(is_component); let server_addr: SocketAddr = "127.0.0.1:0".parse().expect("localhost parses"); let incoming = AddrIncoming::bind(&server_addr).expect("bind"); let bound_port = incoming.local_addr().port(); @@ -197,17 +198,16 @@ async fn custom_ca_works() -> TestResult { StatusCode::SERVICE_UNAVAILABLE ); Ok(()) -} +}); -#[tokio::test(flavor = "multi_thread")] -#[serial_test::serial] -async fn client_certs_work() -> TestResult { +viceroy_test!(client_certs_work, |is_component| { // Set up the test harness std::env::set_var( "SSL_CERT_FILE", concat!(env!("CARGO_MANIFEST_DIR"), "/../test-fixtures/data/ca.pem"), ); - let test = Test::using_fixture("mutual-tls.wasm"); + let test = Test::using_fixture("mutual-tls.wasm").adapt_component(is_component); + let server_addr: SocketAddr = "127.0.0.1:0".parse().expect("localhost parses"); let incoming = AddrIncoming::bind(&server_addr).expect("bind"); let bound_port = incoming.local_addr().port(); @@ -261,4 +261,4 @@ async fn client_certs_work() -> TestResult { std::env::remove_var("SSL_CERT_FILE"); Ok(()) -} +}); diff --git a/cli/tests/integration/common.rs b/cli/tests/integration/common.rs index f9069a3b..f9fedb75 100644 --- a/cli/tests/integration/common.rs +++ b/cli/tests/integration/common.rs @@ -44,6 +44,29 @@ macro_rules! viceroy_test { }; } +/// A variation of `viceroy_test` that will produce one test, and run the core-wasm and component +/// cases sequentially. A failure in either one causes the whole test to fail, which makes things a +/// little bit harder to debug, but for tests like logging that manipulate a shared resource, this +/// is the simplest solution. +#[macro_export] +macro_rules! viceroy_test_sequential { + ($name:ident, |$is_component:ident| $body:block) => { + mod $name { + use super::*; + + async fn test_impl($is_component: bool) -> TestResult { + $body + } + + #[tokio::test(flavor = "multi_thread")] + async fn sequential() -> TestResult { + test_impl(false).await?; + test_impl(true).await + } + } + }; +} + /// A shorthand for the path to our test fixtures' build artifacts for Rust tests. /// /// This value can be appended with the name of a fixture's `.wasm` in a test program, using the diff --git a/cli/tests/integration/config_store_lookup.rs b/cli/tests/integration/config_store_lookup.rs index b6725af4..d0680896 100644 --- a/cli/tests/integration/config_store_lookup.rs +++ b/cli/tests/integration/config_store_lookup.rs @@ -1,8 +1,10 @@ -use crate::common::{Test, TestResult}; +use crate::{ + common::{Test, TestResult}, + viceroy_test, +}; use hyper::{body::to_bytes, StatusCode}; -#[tokio::test(flavor = "multi_thread")] -async fn json_config_store_lookup_works() -> TestResult { +viceroy_test!(json_config_store_lookup_works, |is_component| { const FASTLY_TOML: &str = r#" name = "json-config_store-lookup" description = "json config_store lookup test" @@ -13,8 +15,8 @@ async fn json_config_store_lookup_works() -> TestResult { format = "json" "#; - // let resp = Test::using_fixture("config_store-lookup.wasm") let resp = Test::using_fixture("config-store-lookup.wasm") + .adapt_component(is_component) .using_fastly_toml(FASTLY_TOML)? .against_empty() .await?; @@ -27,10 +29,9 @@ async fn json_config_store_lookup_works() -> TestResult { .is_empty()); Ok(()) -} +}); -#[tokio::test(flavor = "multi_thread")] -async fn inline_toml_config_store_lookup_works() -> TestResult { +viceroy_test!(inline_toml_config_store_lookup_works, |is_component| { const FASTLY_TOML: &str = r#" name = "inline-toml-config_store-lookup" description = "inline toml config_store lookup test" @@ -44,6 +45,7 @@ async fn inline_toml_config_store_lookup_works() -> TestResult { "#; let resp = Test::using_fixture("config-store-lookup.wasm") + .adapt_component(is_component) .using_fastly_toml(FASTLY_TOML)? .against_empty() .await?; @@ -56,10 +58,9 @@ async fn inline_toml_config_store_lookup_works() -> TestResult { .is_empty()); Ok(()) -} +}); -#[tokio::test(flavor = "multi_thread")] -async fn missing_config_store_works() -> TestResult { +viceroy_test!(missing_config_store_works, |is_component| { const FASTLY_TOML: &str = r#" name = "missing-config_store-config" description = "missing config_store test" @@ -67,6 +68,7 @@ async fn missing_config_store_works() -> TestResult { "#; let resp = Test::using_fixture("config-store-lookup.wasm") + .adapt_component(is_component) .using_fastly_toml(FASTLY_TOML)? .against_empty() .await?; @@ -74,4 +76,4 @@ async fn missing_config_store_works() -> TestResult { assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); Ok(()) -} +}); diff --git a/cli/tests/integration/device_detection_lookup.rs b/cli/tests/integration/device_detection_lookup.rs index e06895ba..08938e95 100644 --- a/cli/tests/integration/device_detection_lookup.rs +++ b/cli/tests/integration/device_detection_lookup.rs @@ -1,8 +1,10 @@ -use crate::common::{Test, TestResult}; +use crate::{ + common::{Test, TestResult}, + viceroy_test, +}; use hyper::{body::to_bytes, StatusCode}; -#[tokio::test(flavor = "multi_thread")] -async fn json_device_detection_lookup_works() -> TestResult { +viceroy_test!(json_device_detection_lookup_works, |is_component| { const FASTLY_TOML: &str = r#" name = "json-device-detection-lookup" description = "json device detection lookup test" @@ -15,6 +17,7 @@ async fn json_device_detection_lookup_works() -> TestResult { "#; let resp = Test::using_fixture("device-detection-lookup.wasm") + .adapt_component(is_component) .using_fastly_toml(FASTLY_TOML)? .against_empty() .await?; @@ -27,10 +30,9 @@ async fn json_device_detection_lookup_works() -> TestResult { .is_empty()); Ok(()) -} +}); -#[tokio::test(flavor = "multi_thread")] -async fn inline_toml_device_detection_lookup_works() -> TestResult { +viceroy_test!(inline_toml_device_detection_lookup_works, |is_component| { const FASTLY_TOML: &str = r#" name = "inline-toml-device-detection-lookup" description = "inline toml device detection lookup test" @@ -51,6 +53,7 @@ async fn inline_toml_device_detection_lookup_works() -> TestResult { "#; let resp = Test::using_fixture("device-detection-lookup.wasm") + .adapt_component(is_component) .using_fastly_toml(FASTLY_TOML)? .against_empty() .await?; @@ -63,4 +66,4 @@ async fn inline_toml_device_detection_lookup_works() -> TestResult { .is_empty()); Ok(()) -} +}); diff --git a/cli/tests/integration/dictionary_lookup.rs b/cli/tests/integration/dictionary_lookup.rs index 35e8f2ba..90044dbb 100644 --- a/cli/tests/integration/dictionary_lookup.rs +++ b/cli/tests/integration/dictionary_lookup.rs @@ -1,8 +1,10 @@ -use crate::common::{Test, TestResult}; +use crate::{ + common::{Test, TestResult}, + viceroy_test, +}; use hyper::{body::to_bytes, StatusCode}; -#[tokio::test(flavor = "multi_thread")] -async fn json_dictionary_lookup_works() -> TestResult { +viceroy_test!(json_dictionary_lookup_works, |is_component| { const FASTLY_TOML: &str = r#" name = "json-dictionary-lookup" description = "json dictionary lookup test" @@ -16,6 +18,7 @@ async fn json_dictionary_lookup_works() -> TestResult { "#; let resp = Test::using_fixture("dictionary-lookup.wasm") + .adapt_component(is_component) .using_fastly_toml(FASTLY_TOML)? .against_empty() .await?; @@ -28,10 +31,9 @@ async fn json_dictionary_lookup_works() -> TestResult { .is_empty()); Ok(()) -} +}); -#[tokio::test(flavor = "multi_thread")] -async fn inline_toml_dictionary_lookup_works() -> TestResult { +viceroy_test!(inline_toml_dictionary_lookup_works, |is_component| { const FASTLY_TOML: &str = r#" name = "inline-toml-dictionary-lookup" description = "inline toml dictionary lookup test" @@ -47,6 +49,7 @@ async fn inline_toml_dictionary_lookup_works() -> TestResult { "#; let resp = Test::using_fixture("dictionary-lookup.wasm") + .adapt_component(is_component) .using_fastly_toml(FASTLY_TOML)? .against_empty() .await?; @@ -59,10 +62,9 @@ async fn inline_toml_dictionary_lookup_works() -> TestResult { .is_empty()); Ok(()) -} +}); -#[tokio::test(flavor = "multi_thread")] -async fn missing_dictionary_works() -> TestResult { +viceroy_test!(missing_dictionary_works, |is_component| { const FASTLY_TOML: &str = r#" name = "missing-dictionary-config" description = "missing dictionary test" @@ -70,6 +72,7 @@ async fn missing_dictionary_works() -> TestResult { "#; let resp = Test::using_fixture("dictionary-lookup.wasm") + .adapt_component(is_component) .using_fastly_toml(FASTLY_TOML)? .against_empty() .await?; @@ -77,4 +80,4 @@ async fn missing_dictionary_works() -> TestResult { assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); Ok(()) -} +}); diff --git a/cli/tests/integration/downstream_req.rs b/cli/tests/integration/downstream_req.rs index d6ce355d..1c2e2425 100644 --- a/cli/tests/integration/downstream_req.rs +++ b/cli/tests/integration/downstream_req.rs @@ -1,18 +1,21 @@ use { - crate::common::{Test, TestResult}, + crate::{ + common::{Test, TestResult}, + viceroy_test, + }, hyper::{Request, StatusCode}, }; -#[tokio::test(flavor = "multi_thread")] -async fn downstream_request_works() -> TestResult { +viceroy_test!(downstream_request_works, |is_component| { let req = Request::get("/") .header("Accept", "text/html") .header("X-Custom-Test", "abcdef") .body("Hello, world!")?; let resp = Test::using_fixture("downstream-req.wasm") + .adapt_component(is_component) .against(req) .await?; assert_eq!(resp.status(), StatusCode::OK); Ok(()) -} +}); diff --git a/cli/tests/integration/edge_rate_limiting.rs b/cli/tests/integration/edge_rate_limiting.rs index 4a9e40de..544998c3 100644 --- a/cli/tests/integration/edge_rate_limiting.rs +++ b/cli/tests/integration/edge_rate_limiting.rs @@ -1,15 +1,18 @@ //! Tests related to HTTP request and response bodies. use { - crate::common::{Test, TestResult}, + crate::{ + common::{Test, TestResult}, + viceroy_test, + }, hyper::StatusCode, }; -#[tokio::test(flavor = "multi_thread")] -async fn check_hostcalls_implemented() -> TestResult { +viceroy_test!(check_hostcalls_implemented, |is_component| { let resp = Test::using_fixture("edge-rate-limiting.wasm") + .adapt_component(is_component) .against_empty() .await?; assert_eq!(resp.status(), StatusCode::OK); Ok(()) -} +}); diff --git a/cli/tests/integration/geolocation_lookup.rs b/cli/tests/integration/geolocation_lookup.rs index 0d5597f6..cf4d73cb 100644 --- a/cli/tests/integration/geolocation_lookup.rs +++ b/cli/tests/integration/geolocation_lookup.rs @@ -1,8 +1,10 @@ -use crate::common::{Test, TestResult}; +use crate::{ + common::{Test, TestResult}, + viceroy_test, +}; use hyper::{body::to_bytes, StatusCode}; -#[tokio::test(flavor = "multi_thread")] -async fn json_geolocation_lookup_works() -> TestResult { +viceroy_test!(json_geolocation_lookup_works, |is_component| { const FASTLY_TOML: &str = r#" name = "json-geolocation-lookup" description = "json geolocation lookup test" @@ -16,6 +18,7 @@ async fn json_geolocation_lookup_works() -> TestResult { "#; let resp = Test::using_fixture("geolocation-lookup.wasm") + .adapt_component(is_component) .using_fastly_toml(FASTLY_TOML)? .against_empty() .await?; @@ -28,10 +31,9 @@ async fn json_geolocation_lookup_works() -> TestResult { .is_empty()); Ok(()) -} +}); -#[tokio::test(flavor = "multi_thread")] -async fn inline_toml_geolocation_lookup_works() -> TestResult { +viceroy_test!(inline_toml_geolocation_lookup_works, |is_component| { const FASTLY_TOML: &str = r#" name = "inline-toml-geolocation-lookup" description = "inline toml geolocation lookup test" @@ -83,6 +85,7 @@ async fn inline_toml_geolocation_lookup_works() -> TestResult { "#; let resp = Test::using_fixture("geolocation-lookup.wasm") + .adapt_component(is_component) .using_fastly_toml(FASTLY_TOML)? .against_empty() .await?; @@ -95,28 +98,31 @@ async fn inline_toml_geolocation_lookup_works() -> TestResult { .is_empty()); Ok(()) -} +}); -#[tokio::test(flavor = "multi_thread")] -async fn default_configuration_geolocation_lookup_works() -> TestResult { - const FASTLY_TOML: &str = r#" +viceroy_test!( + default_configuration_geolocation_lookup_works, + |is_component| { + const FASTLY_TOML: &str = r#" name = "default-config-geolocation-lookup" description = "default config geolocation lookup test" authors = ["Test User "] language = "rust" "#; - let resp = Test::using_fixture("geolocation-lookup-default.wasm") - .using_fastly_toml(FASTLY_TOML)? - .against_empty() - .await?; + let resp = Test::using_fixture("geolocation-lookup-default.wasm") + .adapt_component(is_component) + .using_fastly_toml(FASTLY_TOML)? + .against_empty() + .await?; - assert_eq!(resp.status(), StatusCode::OK); - assert!(to_bytes(resp.into_body()) - .await - .expect("can read body") - .to_vec() - .is_empty()); + assert_eq!(resp.status(), StatusCode::OK); + assert!(to_bytes(resp.into_body()) + .await + .expect("can read body") + .to_vec() + .is_empty()); - Ok(()) -} + Ok(()) + } +); diff --git a/cli/tests/integration/grpc.rs b/cli/tests/integration/grpc.rs index 5ca6e7ea..da3dc39f 100644 --- a/cli/tests/integration/grpc.rs +++ b/cli/tests/integration/grpc.rs @@ -1,13 +1,15 @@ -use crate::common::{Test, TestResult}; +use crate::{ + common::{Test, TestResult}, + viceroy_test, +}; use hyper::http::response; use hyper::server::conn::AddrIncoming; use hyper::service::{make_service_fn, service_fn}; use hyper::{Request, Server, StatusCode}; use std::net::SocketAddr; -#[tokio::test(flavor = "multi_thread")] -async fn grpc_creates_h2_connection() -> TestResult { - let test = Test::using_fixture("grpc.wasm"); +viceroy_test!(grpc_creates_h2_connection, |is_component| { + let test = Test::using_fixture("grpc.wasm").adapt_component(is_component); let server_addr: SocketAddr = "127.0.0.1:0".parse().expect("localhost parses"); let incoming = AddrIncoming::bind(&server_addr).expect("bind"); let bound_port = incoming.local_addr().port(); @@ -39,4 +41,4 @@ async fn grpc_creates_h2_connection() -> TestResult { // assert_eq!(resp.into_body().read_into_string().await?, "Hello!"); Ok(()) -} +}); diff --git a/cli/tests/integration/http_semantics.rs b/cli/tests/integration/http_semantics.rs index ead4cd87..6fcc4afb 100644 --- a/cli/tests/integration/http_semantics.rs +++ b/cli/tests/integration/http_semantics.rs @@ -1,14 +1,17 @@ //! Tests related to HTTP semantics (e.g. framing headers, status codes). use { - crate::common::{Test, TestResult}, + crate::{ + common::{Test, TestResult}, + viceroy_test, + }, hyper::{header, Request, Response, StatusCode}, }; -#[tokio::test(flavor = "multi_thread")] -async fn framing_headers_are_overridden() -> TestResult { +viceroy_test!(framing_headers_are_overridden, |is_component| { // Set up the test harness let test = Test::using_fixture("bad-framing-headers.wasm") + .adapt_component(is_component) // The "TheOrigin" backend checks framing headers on the request and then echos its body. .backend("TheOrigin", "/", None, |req| { assert!(!req.headers().contains_key(header::TRANSFER_ENCODING)); @@ -34,12 +37,12 @@ async fn framing_headers_are_overridden() -> TestResult { ); Ok(()) -} +}); -#[tokio::test(flavor = "multi_thread")] -async fn content_length_is_computed_correctly() -> TestResult { +viceroy_test!(content_length_is_computed_correctly, |is_component| { // Set up the test harness let test = Test::using_fixture("content-length.wasm") + .adapt_component(is_component) // The "TheOrigin" backend supplies a fixed-size body. .backend("TheOrigin", "/", None, |_| { Response::new(Vec::from(&b"ABCDEFGHIJKLMNOPQRST"[..])) @@ -59,4 +62,4 @@ async fn content_length_is_computed_correctly() -> TestResult { assert_eq!(resp_body, "ABCD12345xyzEFGHIJKLMNOPQRST"); Ok(()) -} +}); diff --git a/cli/tests/integration/kv_store.rs b/cli/tests/integration/kv_store.rs index f535b98e..85dc2d2a 100644 --- a/cli/tests/integration/kv_store.rs +++ b/cli/tests/integration/kv_store.rs @@ -1,8 +1,10 @@ -use crate::common::{Test, TestResult}; +use crate::{ + common::{Test, TestResult}, + viceroy_test, +}; use hyper::{body::to_bytes, StatusCode}; -#[tokio::test(flavor = "multi_thread")] -async fn kv_store() -> TestResult { +viceroy_test!(kv_store, |is_component| { const FASTLY_TOML: &str = r#" name = "kv-store-test" description = "kv store test" @@ -14,6 +16,7 @@ async fn kv_store() -> TestResult { "#; let resp = Test::using_fixture("kv_store.wasm") + .adapt_component(is_component) .using_fastly_toml(FASTLY_TOML)? .against_empty() .await?; @@ -26,10 +29,9 @@ async fn kv_store() -> TestResult { .is_empty()); Ok(()) -} +}); -#[tokio::test(flavor = "multi_thread")] -async fn object_stores_backward_compat() -> TestResult { +viceroy_test!(object_stores_backward_compat, |is_component| { // Previously the "kv_stores" key was named "object_stores" and // the "file" key was named "path". This test ensures that both // still work. @@ -44,6 +46,7 @@ async fn object_stores_backward_compat() -> TestResult { "#; let resp = Test::using_fixture("kv_store.wasm") + .adapt_component(is_component) .using_fastly_toml(FASTLY_TOML)? .against_empty() .await?; @@ -56,9 +59,9 @@ async fn object_stores_backward_compat() -> TestResult { .is_empty()); Ok(()) -} -#[tokio::test(flavor = "multi_thread")] -async fn object_store_backward_compat() -> TestResult { +}); + +viceroy_test!(object_store_backward_compat, |is_component| { // Previously the "object_stores" key was named "object_store" and // the "file" key was named "path". This test ensures that both // still work. @@ -73,6 +76,7 @@ async fn object_store_backward_compat() -> TestResult { "#; let resp = Test::using_fixture("kv_store.wasm") + .adapt_component(is_component) .using_fastly_toml(FASTLY_TOML)? .against_empty() .await?; @@ -85,10 +89,9 @@ async fn object_store_backward_compat() -> TestResult { .is_empty()); Ok(()) -} +}); -#[tokio::test(flavor = "multi_thread")] -async fn kv_store_bad_configs() -> TestResult { +viceroy_test!(kv_store_bad_configs, |is_component| { const BAD_1_FASTLY_TOML: &str = r#" name = "kv-store-test" description = "kv store test" @@ -97,7 +100,10 @@ async fn kv_store_bad_configs() -> TestResult { [local_server] kv_stores.store_one = [{key = 3, data = "This is some data"}] "#; - match Test::using_fixture("kv_store.wasm").using_fastly_toml(BAD_1_FASTLY_TOML) { + match Test::using_fixture("kv_store.wasm") + .adapt_component(is_component) + .using_fastly_toml(BAD_1_FASTLY_TOML) + { Err(e) => assert_eq!( "invalid configuration for 'store_one': The `key` value for an object is not a string.", &e.to_string() @@ -113,7 +119,9 @@ async fn kv_store_bad_configs() -> TestResult { [local_server] kv_stores.store_one = [{key = "first", data = 3}] "#; - match Test::using_fixture("kv_store.wasm").using_fastly_toml(BAD_2_FASTLY_TOML) { + match Test::using_fixture("kv_store.wasm") + .adapt_component(is_component) + .using_fastly_toml(BAD_2_FASTLY_TOML) { Err(e) => assert_eq!("invalid configuration for 'store_one': The `data` value for the object `first` is not a string.", &e.to_string()), _ => panic!(), } @@ -126,7 +134,7 @@ async fn kv_store_bad_configs() -> TestResult { [local_server] kv_stores.store_one = [{key = "first", data = "This is some data", file = "../test-fixtures/data/kv-store.txt"}] "#; - match Test::using_fixture("kv_store.wasm").using_fastly_toml(BAD_3_FASTLY_TOML) { + match Test::using_fixture("kv_store.wasm").adapt_component(is_component).using_fastly_toml(BAD_3_FASTLY_TOML) { Err(e) => assert_eq!("invalid configuration for 'store_one': The `file` and `data` keys for the object `first` are set. Only one can be used.", &e.to_string()), _ => panic!(), } @@ -139,7 +147,7 @@ async fn kv_store_bad_configs() -> TestResult { [local_server] kv_stores.store_one = [{key = "first", file = 3}] "#; - match Test::using_fixture("kv_store.wasm").using_fastly_toml(BAD_4_FASTLY_TOML) { + match Test::using_fixture("kv_store.wasm").adapt_component(is_component).using_fastly_toml(BAD_4_FASTLY_TOML) { Err(e) => assert_eq!("invalid configuration for 'store_one': The `file` value for the object `first` is not a string.", &e.to_string()), _ => panic!(), } @@ -164,7 +172,10 @@ async fn kv_store_bad_configs() -> TestResult { #[cfg(target_os = "windows")] let invalid_path_message = "invalid configuration for 'store_one': The system cannot find the path specified. (os error 3)"; - match Test::using_fixture("kv_store.wasm").using_fastly_toml(BAD_5_FASTLY_TOML) { + match Test::using_fixture("kv_store.wasm") + .adapt_component(is_component) + .using_fastly_toml(BAD_5_FASTLY_TOML) + { Err(e) => assert_eq!(invalid_path_message, &e.to_string()), _ => panic!(), } @@ -177,7 +188,7 @@ async fn kv_store_bad_configs() -> TestResult { [local_server] kv_stores.store_one = [{key = "first"}] "#; - match Test::using_fixture("kv_store.wasm").using_fastly_toml(BAD_6_FASTLY_TOML) { + match Test::using_fixture("kv_store.wasm").adapt_component(is_component).using_fastly_toml(BAD_6_FASTLY_TOML) { Err(e) => assert_eq!("invalid configuration for 'store_one': The `file` or `data` key for the object `first` is not set. One must be used.", &e.to_string()), _ => panic!(), } @@ -190,7 +201,7 @@ async fn kv_store_bad_configs() -> TestResult { [local_server] kv_stores.store_one = [{data = "This is some data"}] "#; - match Test::using_fixture("kv_store.wasm").using_fastly_toml(BAD_7_FASTLY_TOML) { + match Test::using_fixture("kv_store.wasm").adapt_component(is_component).using_fastly_toml(BAD_7_FASTLY_TOML) { Err(e) => assert_eq!("invalid configuration for 'store_one': The `key` key for an object is not set. It must be used.", &e.to_string()), _ => panic!(), } @@ -203,7 +214,7 @@ async fn kv_store_bad_configs() -> TestResult { [local_server] kv_stores.store_one = "lol lmao" "#; - match Test::using_fixture("kv_store.wasm").using_fastly_toml(BAD_8_FASTLY_TOML) { + match Test::using_fixture("kv_store.wasm").adapt_component(is_component).using_fastly_toml(BAD_8_FASTLY_TOML) { Err(e) => assert_eq!("invalid configuration for 'store_one': There is no array of objects for the given store.", &e.to_string()), _ => panic!(), } @@ -216,16 +227,15 @@ async fn kv_store_bad_configs() -> TestResult { [local_server] kv_stores.store_one = ["This is some data"] "#; - match Test::using_fixture("kv_store.wasm").using_fastly_toml(BAD_9_FASTLY_TOML) { + match Test::using_fixture("kv_store.wasm").adapt_component(is_component).using_fastly_toml(BAD_9_FASTLY_TOML) { Err(e) => assert_eq!("invalid configuration for 'store_one': There is an object in the given store that is not a table of keys.", &e.to_string()), _ => panic!(), } Ok(()) -} +}); -#[tokio::test(flavor = "multi_thread")] -async fn kv_store_bad_key_values() -> TestResult { +viceroy_test!(kv_store_bad_key_values, |is_component| { const BAD_1_FASTLY_TOML: &str = r#" name = "kv-store-test" description = "kv store test" @@ -234,7 +244,7 @@ async fn kv_store_bad_key_values() -> TestResult { [local_server] kv_stores.store_one = [{key = "", data = "This is some data"}] "#; - match Test::using_fixture("kv_store.wasm").using_fastly_toml(BAD_1_FASTLY_TOML) { + match Test::using_fixture("kv_store.wasm").adapt_component(is_component).using_fastly_toml(BAD_1_FASTLY_TOML) { Err(e) => assert_eq!("invalid configuration for 'store_one': Invalid `key` value used: Keys for objects cannot be empty.", &e.to_string()), _ => panic!(), } @@ -247,7 +257,7 @@ async fn kv_store_bad_key_values() -> TestResult { [local_server] kv_stores.store_one = [{key = "LOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,keeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeEEEEEEEEEEEEEEEEEEEEEEEEEEEEEeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeey", data = "This is some data"}] "#; - match Test::using_fixture("kv_store.wasm").using_fastly_toml(BAD_2_FASTLY_TOML) { + match Test::using_fixture("kv_store.wasm").adapt_component(is_component).using_fastly_toml(BAD_2_FASTLY_TOML) { Err(e) => assert_eq!( "invalid configuration for 'store_one': Invalid `key` value used: Keys for objects cannot be over 1024 bytes in size.", &e.to_string() @@ -263,7 +273,7 @@ async fn kv_store_bad_key_values() -> TestResult { [local_server] kv_stores.store_one = [{key = ".well-known/acme-challenge/wheeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", data = "This is some data"}] "#; - match Test::using_fixture("kv_store.wasm").using_fastly_toml(BAD_3_FASTLY_TOML) { + match Test::using_fixture("kv_store.wasm").adapt_component(is_component).using_fastly_toml(BAD_3_FASTLY_TOML) { Err(e) => assert_eq!( "invalid configuration for 'store_one': Invalid `key` value used: Keys for objects cannot start with `.well-known/acme-challenge`.", &e.to_string() @@ -279,7 +289,7 @@ async fn kv_store_bad_key_values() -> TestResult { [local_server] kv_stores.store_one = [{key = ".", data = "This is some data"}] "#; - match Test::using_fixture("kv_store.wasm").using_fastly_toml(BAD_4_FASTLY_TOML) { + match Test::using_fixture("kv_store.wasm").adapt_component(is_component).using_fastly_toml(BAD_4_FASTLY_TOML) { Err(e) => assert_eq!("invalid configuration for 'store_one': Invalid `key` value used: Keys for objects cannot be named `.`.", &e.to_string()), _ => panic!(), } @@ -292,7 +302,7 @@ async fn kv_store_bad_key_values() -> TestResult { [local_server] kv_stores.store_one = [{key = "..", data = "This is some data"}] "#; - match Test::using_fixture("kv_store.wasm").using_fastly_toml(BAD_5_FASTLY_TOML) { + match Test::using_fixture("kv_store.wasm").adapt_component(is_component).using_fastly_toml(BAD_5_FASTLY_TOML) { Err(e) => assert_eq!("invalid configuration for 'store_one': Invalid `key` value used: Keys for objects cannot be named `..`.", &e.to_string()), _ => panic!(), } @@ -305,7 +315,7 @@ async fn kv_store_bad_key_values() -> TestResult { [local_server] kv_stores.store_one = [{key = "carriage\rreturn", data = "This is some data"}] "#; - match Test::using_fixture("kv_store.wasm").using_fastly_toml(BAD_6_FASTLY_TOML) { + match Test::using_fixture("kv_store.wasm").adapt_component(is_component).using_fastly_toml(BAD_6_FASTLY_TOML) { Err(e) => assert_eq!("invalid configuration for 'store_one': Invalid `key` value used: Keys for objects cannot contain a `\r`.", &e.to_string()), _ => panic!(), } @@ -318,7 +328,7 @@ async fn kv_store_bad_key_values() -> TestResult { [local_server] kv_stores.store_one = [{key = "newlines\nin\nthis\neconomy?", data = "This is some data"}] "#; - match Test::using_fixture("kv_store.wasm").using_fastly_toml(BAD_7_FASTLY_TOML) { + match Test::using_fixture("kv_store.wasm").adapt_component(is_component).using_fastly_toml(BAD_7_FASTLY_TOML) { Err(e) => assert_eq!("invalid configuration for 'store_one': Invalid `key` value used: Keys for objects cannot contain a `\n`.", &e.to_string()), _ => panic!(), } @@ -331,7 +341,7 @@ async fn kv_store_bad_key_values() -> TestResult { [local_server] kv_stores.store_one = [{key = "howdy[", data = "This is some data"}] "#; - match Test::using_fixture("kv_store.wasm").using_fastly_toml(BAD_8_FASTLY_TOML) { + match Test::using_fixture("kv_store.wasm").adapt_component(is_component).using_fastly_toml(BAD_8_FASTLY_TOML) { Err(e) => assert_eq!("invalid configuration for 'store_one': Invalid `key` value used: Keys for objects cannot contain a `[`.", &e.to_string()), _ => panic!(), } @@ -344,7 +354,7 @@ async fn kv_store_bad_key_values() -> TestResult { [local_server] kv_stores.store_one = [{key = "hello]", data = "This is some data"}] "#; - match Test::using_fixture("kv_store.wasm").using_fastly_toml(BAD_9_FASTLY_TOML) { + match Test::using_fixture("kv_store.wasm").adapt_component(is_component).using_fastly_toml(BAD_9_FASTLY_TOML) { Err(e) => assert_eq!("invalid configuration for 'store_one': Invalid `key` value used: Keys for objects cannot contain a `]`.", &e.to_string()), _ => panic!(), } @@ -357,7 +367,7 @@ async fn kv_store_bad_key_values() -> TestResult { [local_server] kv_stores.store_one = [{key = "yoohoo*", data = "This is some data"}] "#; - match Test::using_fixture("kv_store.wasm").using_fastly_toml(BAD_10_FASTLY_TOML) { + match Test::using_fixture("kv_store.wasm").adapt_component(is_component).using_fastly_toml(BAD_10_FASTLY_TOML) { Err(e) => assert_eq!("invalid configuration for 'store_one': Invalid `key` value used: Keys for objects cannot contain a `*`.", &e.to_string()), _ => panic!(), } @@ -370,7 +380,7 @@ async fn kv_store_bad_key_values() -> TestResult { [local_server] kv_stores.store_one = [{key = "hey?", data = "This is some data"}] "#; - match Test::using_fixture("kv_store.wasm").using_fastly_toml(BAD_11_FASTLY_TOML) { + match Test::using_fixture("kv_store.wasm").adapt_component(is_component).using_fastly_toml(BAD_11_FASTLY_TOML) { Err(e) => assert_eq!("invalid configuration for 'store_one': Invalid `key` value used: Keys for objects cannot contain a `?`.", &e.to_string()), _ => panic!(), } @@ -383,10 +393,10 @@ async fn kv_store_bad_key_values() -> TestResult { [local_server] kv_stores.store_one = [{key = "ello ello#", data = "This is some data"}] "#; - match Test::using_fixture("kv_store.wasm").using_fastly_toml(BAD_12_FASTLY_TOML) { + match Test::using_fixture("kv_store.wasm").adapt_component(is_component).using_fastly_toml(BAD_12_FASTLY_TOML) { Err(e) => assert_eq!("invalid configuration for 'store_one': Invalid `key` value used: Keys for objects cannot contain a `#`.", &e.to_string()), _ => panic!(), } Ok(()) -} +}); diff --git a/cli/tests/integration/logging.rs b/cli/tests/integration/logging.rs index be4326ec..22b4621b 100644 --- a/cli/tests/integration/logging.rs +++ b/cli/tests/integration/logging.rs @@ -1,5 +1,8 @@ use { - crate::common::{Test, TestResult}, + crate::{ + common::{Test, TestResult}, + viceroy_test_sequential, + }, hyper::StatusCode, std::{ io::{self, Write}, @@ -29,10 +32,10 @@ fn setup_log_writer() -> mpsc::Receiver> { recv } -#[tokio::test(flavor = "multi_thread")] -async fn logging_works() -> TestResult { +viceroy_test_sequential!(logging_works, |is_component| { let log_recv = setup_log_writer(); let resp = Test::using_fixture("logging.wasm") + .adapt_component(is_component) .log_stderr() .log_stdout() .against_empty() @@ -68,4 +71,4 @@ async fn logging_works() -> TestResult { assert_eq!(read_log_line(), "stderr :: on each write\n"); Ok(()) -} +}); diff --git a/cli/tests/integration/secret_store.rs b/cli/tests/integration/secret_store.rs index b6d8900f..25ef674a 100644 --- a/cli/tests/integration/secret_store.rs +++ b/cli/tests/integration/secret_store.rs @@ -1,10 +1,12 @@ -use crate::common::{Test, TestResult}; +use crate::{ + common::{Test, TestResult}, + viceroy_test, +}; use hyper::{body::to_bytes, StatusCode}; use viceroy_lib::config::FastlyConfig; use viceroy_lib::error::{FastlyConfigError, SecretStoreConfigError}; -#[tokio::test(flavor = "multi_thread")] -async fn secret_store_works() -> TestResult { +viceroy_test!(secret_store_works, |is_component| { const FASTLY_TOML: &str = r#" name = "secret-store" description = "secret store test" @@ -15,6 +17,7 @@ async fn secret_store_works() -> TestResult { "#; let resp = Test::using_fixture("secret-store.wasm") + .adapt_component(is_component) .using_fastly_toml(FASTLY_TOML)? .against_empty() .await?; @@ -27,7 +30,7 @@ async fn secret_store_works() -> TestResult { .is_empty()); Ok(()) -} +}); fn bad_config_test(toml_fragment: &str) -> Result { let toml = format!( diff --git a/cli/tests/integration/sending_response.rs b/cli/tests/integration/sending_response.rs index 35d775fd..ca147d17 100644 --- a/cli/tests/integration/sending_response.rs +++ b/cli/tests/integration/sending_response.rs @@ -1,38 +1,43 @@ //! Tests related to sending HTTP responses downstream. use { - crate::common::{Test, TestResult}, + crate::{ + common::{Test, TestResult}, + viceroy_test, + }, hyper::{ body::{to_bytes, HttpBody}, StatusCode, }, }; -/// Use the `teapot-status` fixture to check that responses can be sent downstream by the guest. -/// -/// `teapot-status.wasm` will create a [`418 I'm a teapot`][tea] response, per [RFC2324][rfc]. This -/// status code is used to clearly indicate that a response came from the guest program. -/// -/// [rfc]: https://tools.ietf.org/html/rfc2324 -/// [tea]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/418 -#[tokio::test(flavor = "multi_thread")] -async fn responses_can_be_sent_downstream() -> TestResult { +// Use the `teapot-status` fixture to check that responses can be sent downstream by the guest. +// +// `teapot-status.wasm` will create a [`418 I'm a teapot`][tea] response, per [RFC2324][rfc]. This +// status code is used to clearly indicate that a response came from the guest program. +// +// [rfc]: https://tools.ietf.org/html/rfc2324 +// [tea]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/418 +viceroy_test!(responses_can_be_sent_downstream, |is_component| { let resp = Test::using_fixture("teapot-status.wasm") + .adapt_component(is_component) .against_empty() .await?; assert_eq!(resp.status(), StatusCode::IM_A_TEAPOT); Ok(()) -} +}); -/// Run a program that does nothing, to check that an empty response is sent downstream by default. -/// -/// `noop.wasm` is an empty guest program. This exists to show that if no response is sent -/// downstream by the guest, a [`200 OK`][ok] response will be sent. -/// -/// [ok]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 -#[tokio::test(flavor = "multi_thread")] -async fn empty_ok_response_by_default() -> TestResult { - let resp = Test::using_fixture("noop.wasm").against_empty().await?; +// Run a program that does nothing, to check that an empty response is sent downstream by default. +// +// `noop.wasm` is an empty guest program. This exists to show that if no response is sent +// downstream by the guest, a [`200 OK`][ok] response will be sent. +// +// [ok]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 +viceroy_test!(empty_ok_response_by_default, |is_component| { + let resp = Test::using_fixture("noop.wasm") + .adapt_component(is_component) + .against_empty() + .await?; assert_eq!(resp.status(), StatusCode::OK); assert!(to_bytes(resp.into_body()) @@ -42,23 +47,25 @@ async fn empty_ok_response_by_default() -> TestResult { .is_empty()); Ok(()) -} +}); -/// Run a program that panics, to check that a [`500 Internal Server Error`][err] response is sent -/// downstream. -/// -/// [err]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 -#[tokio::test(flavor = "multi_thread")] -async fn five_hundred_when_guest_panics() -> TestResult { - let resp = Test::using_fixture("panic.wasm").against_empty().await?; +// Run a program that panics, to check that a [`500 Internal Server Error`][err] response is sent +// downstream. +// +// [err]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 +viceroy_test!(five_hundred_when_guest_panics, |is_component| { + let resp = Test::using_fixture("panic.wasm") + .adapt_component(is_component) + .against_empty() + .await?; assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); Ok(()) -} +}); -/// Test that gradually writing to a streaming body works. -#[tokio::test(flavor = "multi_thread")] -async fn responses_can_be_streamed_downstream() -> TestResult { +// Test that gradually writing to a streaming body works. +viceroy_test!(responses_can_be_streamed_downstream, |is_component| { let mut resp = Test::using_fixture("streaming-response.wasm") + .adapt_component(is_component) .via_hyper() .against_empty() .await?; @@ -85,4 +92,4 @@ async fn responses_can_be_streamed_downstream() -> TestResult { assert_eq!(i, 1000); Ok(()) -} +}); diff --git a/cli/tests/integration/sleep.rs b/cli/tests/integration/sleep.rs index 4c49c591..cf58819d 100644 --- a/cli/tests/integration/sleep.rs +++ b/cli/tests/integration/sleep.rs @@ -1,12 +1,18 @@ -use crate::common::{Test, TestResult}; +use crate::{ + common::{Test, TestResult}, + viceroy_test, +}; use hyper::{body::to_bytes, StatusCode}; -/// Run a program that only sleeps. This exercises async functionality in wasi. -/// Check that an empty response is sent downstream by default. -/// -/// `sleep.wasm` is a guest program which sleeps for 100 milliseconds,then returns. -#[tokio::test(flavor = "multi_thread")] -async fn empty_ok_response_by_default_after_sleep() -> TestResult { - let resp = Test::using_fixture("sleep.wasm").against_empty().await?; + +// Run a program that only sleeps. This exercises async functionality in wasi. +// Check that an empty response is sent downstream by default. +// +// `sleep.wasm` is a guest program which sleeps for 100 milliseconds,then returns. +viceroy_test!(empty_ok_response_by_default_after_sleep, |is_component| { + let resp = Test::using_fixture("sleep.wasm") + .adapt_component(is_component) + .against_empty() + .await?; assert_eq!(resp.status(), StatusCode::OK); assert!(to_bytes(resp.into_body()) @@ -16,4 +22,4 @@ async fn empty_ok_response_by_default_after_sleep() -> TestResult { .is_empty()); Ok(()) -} +}); diff --git a/cli/tests/integration/upstream.rs b/cli/tests/integration/upstream.rs index bb638d59..1cb56723 100644 --- a/cli/tests/integration/upstream.rs +++ b/cli/tests/integration/upstream.rs @@ -1,19 +1,22 @@ use { - crate::common::{Test, TestResult}, + crate::{ + common::{Test, TestResult}, + viceroy_test, + }, hyper::{ header::{self, HeaderValue}, Request, Response, StatusCode, }, }; -#[tokio::test(flavor = "multi_thread")] -async fn upstream_sync() -> TestResult { +viceroy_test!(upstream_sync, |is_component| { //////////////////////////////////////////////////////////////////////////////////// // Setup //////////////////////////////////////////////////////////////////////////////////// // Set up the test harness let test = Test::using_fixture("upstream.wasm") + .adapt_component(is_component) // The "origin" backend simply echos the request body .backend("origin", "/", None, |req| { let body = req.into_body(); @@ -120,12 +123,12 @@ async fn upstream_sync() -> TestResult { assert!(resp.status().is_server_error()); Ok(()) -} +}); -#[tokio::test(flavor = "multi_thread")] -async fn override_host_works() -> TestResult { +viceroy_test!(override_host_works, |is_component| { // Set up the test harness let test = Test::using_fixture("upstream.wasm") + .adapt_component(is_component) .backend("override-host", "/", Some("otherhost.com"), |req| { assert_eq!( req.headers().get(header::HOST), @@ -148,12 +151,12 @@ async fn override_host_works() -> TestResult { assert_eq!(resp.status(), StatusCode::OK); Ok(()) -} +}); -/// Test that we can transparently gunzip responses when required. -#[tokio::test(flavor = "multi_thread")] -async fn transparent_gunzip() -> TestResult { +// Test that we can transparently gunzip responses when required. +viceroy_test!(transparent_gunzip, |is_component| { let resp = Test::using_fixture("gzipped-response.wasm") + .adapt_component(is_component) .backend("echo", "/", None, |mut req| { let mut response_builder = Response::builder(); @@ -179,4 +182,4 @@ async fn transparent_gunzip() -> TestResult { ); Ok(()) -} +}); diff --git a/cli/tests/integration/upstream_async.rs b/cli/tests/integration/upstream_async.rs index bb21442a..2acd3f82 100644 --- a/cli/tests/integration/upstream_async.rs +++ b/cli/tests/integration/upstream_async.rs @@ -3,12 +3,14 @@ use std::sync::Arc; use tokio::sync::Semaphore; use { - crate::common::{Test, TestResult}, + crate::{ + common::{Test, TestResult}, + viceroy_test, + }, hyper::{Response, StatusCode}, }; -#[tokio::test(flavor = "multi_thread")] -async fn upstream_async_methods() -> TestResult { +viceroy_test!(upstream_async_methods, |is_component| { // Set up two backends that share a semaphore that starts with zero permits. `backend1` must // take a semaphore permit and then "forget" it before returning its response. `backend2` adds a // permit to the semaphore and promptly returns. This relationship allows the test fixtures to @@ -17,6 +19,7 @@ async fn upstream_async_methods() -> TestResult { let sema_backend1 = Arc::new(Semaphore::new(0)); let sema_backend2 = sema_backend1.clone(); let test = Test::using_fixture("upstream-async.wasm") + .adapt_component(is_component) .async_backend("backend1", "/", None, move |_| { let sema_backend1 = sema_backend1.clone(); Box::new(async move { @@ -46,4 +49,4 @@ async fn upstream_async_methods() -> TestResult { let resp = test.against_empty().await?; assert_eq!(resp.status(), StatusCode::OK); Ok(()) -} +}); diff --git a/cli/tests/integration/upstream_dynamic.rs b/cli/tests/integration/upstream_dynamic.rs index 757f840b..eb8b4715 100644 --- a/cli/tests/integration/upstream_dynamic.rs +++ b/cli/tests/integration/upstream_dynamic.rs @@ -1,19 +1,22 @@ use { - crate::common::{Test, TestResult}, + crate::{ + common::{Test, TestResult}, + viceroy_test, + }, hyper::{ header::{self, HeaderValue}, Request, Response, StatusCode, }, }; -#[tokio::test(flavor = "multi_thread")] -async fn upstream_sync() -> TestResult { +viceroy_test!(upstream_sync, |is_component| { //////////////////////////////////////////////////////////////////////////////////// // Setup //////////////////////////////////////////////////////////////////////////////////// // Set up the test harness let test = Test::using_fixture("upstream-dynamic.wasm") + .adapt_component(is_component) // The "origin" backend simply echos the request body .backend("origin", "/", None, |req| { let body = req.into_body(); @@ -62,12 +65,12 @@ async fn upstream_sync() -> TestResult { ); Ok(()) -} +}); -#[tokio::test(flavor = "multi_thread")] -async fn override_host_works() -> TestResult { +viceroy_test!(override_host_works, |is_component| { // Set up the test harness let test = Test::using_fixture("upstream-dynamic.wasm") + .adapt_component(is_component) .backend( "override-host", "/", @@ -99,12 +102,12 @@ async fn override_host_works() -> TestResult { assert_eq!(resp.status(), StatusCode::OK); Ok(()) -} +}); -#[tokio::test(flavor = "multi_thread")] -async fn duplication_errors_right() -> TestResult { +viceroy_test!(duplication_errors_right, |is_component| { // Set up the test harness let test = Test::using_fixture("upstream-dynamic.wasm") + .adapt_component(is_component) .backend("static", "/", None, |_| Response::new(vec![])) .await; // Make sure the backends are started so we can know where to direct the request @@ -137,4 +140,4 @@ async fn duplication_errors_right() -> TestResult { assert_eq!(resp.status(), StatusCode::CONFLICT); Ok(()) -} +}); diff --git a/cli/tests/integration/upstream_streaming.rs b/cli/tests/integration/upstream_streaming.rs index f274950a..11cbb64e 100644 --- a/cli/tests/integration/upstream_streaming.rs +++ b/cli/tests/integration/upstream_streaming.rs @@ -1,13 +1,16 @@ use { - crate::common::{Test, TestResult}, + crate::{ + common::{Test, TestResult}, + viceroy_test, + }, hyper::{body::HttpBody, Response, StatusCode}, }; -/// Test that guests can stream a body into an upstream request. -#[tokio::test(flavor = "multi_thread")] -async fn upstream_streaming() -> TestResult { +// Test that guests can stream a body into an upstream request. +viceroy_test!(upstream_streaming, |is_component| { // Set up the test harness let test = Test::using_fixture("upstream-streaming.wasm") + .adapt_component(is_component) // The "origin" backend simply echos the request body .backend("origin", "/", None, |req| Response::new(req.into_body())) .await; @@ -32,4 +35,4 @@ async fn upstream_streaming() -> TestResult { assert_eq!(i, 1000); Ok(()) -} +}); diff --git a/crates/adapter/src/fastly/core.rs b/crates/adapter/src/fastly/core.rs index 28124247..9982e4bc 100644 --- a/crates/adapter/src/fastly/core.rs +++ b/crates/adapter/src/fastly/core.rs @@ -1232,13 +1232,26 @@ pub mod fastly_http_req { nwritten: *mut usize, ) -> FastlyStatus { let name = unsafe { slice::from_raw_parts(name, name_len) }; - alloc_result_opt!(value, value_max_len, nwritten, { - fastly::api::http_req::header_value_get( - req_handle, - name, - u64::try_from(value_max_len).trapping_unwrap(), - ) - }) + with_buffer!( + value, + value_max_len, + { + fastly::api::http_req::header_value_get( + req_handle, + name, + u64::try_from(value_max_len).trapping_unwrap(), + ) + }, + |res| { + let res = + handle_buffer_len!(res, nwritten).ok_or(FastlyStatus::INVALID_ARGUMENT)?; + unsafe { + *nwritten = res.len(); + } + + std::mem::forget(res); + } + ) } #[export_name = "fastly_http_req#header_remove"] @@ -1336,7 +1349,7 @@ pub mod fastly_http_req { Err((detail, e)) => { unsafe { *error_detail = detail - .unwrap_or_else(|| http_req::SendErrorDetailTag::Ok.into()) + .unwrap_or_else(|| http_req::SendErrorDetailTag::Uninitialized.into()) .into(); *resp_handle_out = INVALID_HANDLE; *resp_body_handle_out = INVALID_HANDLE; @@ -1546,7 +1559,7 @@ pub mod fastly_http_req { Err((detail, e)) => { unsafe { *error_detail = detail - .unwrap_or_else(|| http_req::SendErrorDetailTag::Ok.into()) + .unwrap_or_else(|| http_req::SendErrorDetailTag::Uninitialized.into()) .into(); *is_done_out = 0; *resp_handle_out = INVALID_HANDLE; @@ -1604,7 +1617,7 @@ pub mod fastly_http_req { Err((detail, e)) => { unsafe { *error_detail = detail - .unwrap_or_else(|| http_req::SendErrorDetailTag::Ok.into()) + .unwrap_or_else(|| http_req::SendErrorDetailTag::Uninitialized.into()) .into(); *resp_handle_out = INVALID_HANDLE; *resp_body_handle_out = INVALID_HANDLE; @@ -1634,7 +1647,7 @@ pub mod fastly_http_req { Err((detail, e)) => { unsafe { *error_detail = detail - .unwrap_or_else(|| http_req::SendErrorDetailTag::Ok.into()) + .unwrap_or_else(|| http_req::SendErrorDetailTag::Uninitialized.into()) .into(); *resp_handle_out = INVALID_HANDLE; *resp_body_handle_out = INVALID_HANDLE; @@ -1759,13 +1772,26 @@ pub mod fastly_http_resp { nwritten: *mut usize, ) -> FastlyStatus { let name = unsafe { slice::from_raw_parts(name, name_len) }; - alloc_result_opt!(value, value_max_len, nwritten, { - http_resp::header_value_get( - resp_handle, - name, - u64::try_from(value_max_len).trapping_unwrap(), - ) - }) + with_buffer!( + value, + value_max_len, + { + http_resp::header_value_get( + resp_handle, + name, + u64::try_from(value_max_len).trapping_unwrap(), + ) + }, + |res| { + let res = + handle_buffer_len!(res, nwritten).ok_or(FastlyStatus::INVALID_ARGUMENT)?; + unsafe { + *nwritten = res.len(); + } + + std::mem::forget(res); + } + ) } #[export_name = "fastly_http_resp#header_values_get"] @@ -2028,7 +2054,7 @@ pub mod fastly_device_detection { nwritten_out: *mut usize, ) -> FastlyStatus { let user_agent = unsafe { slice::from_raw_parts(user_agent, user_agent_max_len) }; - alloc_result!(buf, buf_len, nwritten_out, { + alloc_result_opt!(buf, buf_len, nwritten_out, { device_detection::lookup(user_agent, u64::try_from(buf_len).trapping_unwrap()) }) } @@ -2177,7 +2203,7 @@ pub mod fastly_kv_store { *kv_store_handle_out = INVALID_HANDLE; } - FastlyStatus::NONE + FastlyStatus::INVALID_ARGUMENT } Ok(Some(res)) => { unsafe { @@ -2659,7 +2685,7 @@ pub mod fastly_async_io { unsafe { *done_index_out = u32::MAX; } - FastlyStatus::NONE + FastlyStatus::OK } Err(e) => e.into(), } diff --git a/lib/data/viceroy-component-adapter.wasm b/lib/data/viceroy-component-adapter.wasm index f1e12673f137d13621d9efe243f94031970a387a..402c4b2cc70306f5ffb3bfb9856c6866bba8ee0b 100755 GIT binary patch delta 26621 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 z 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 Tbmy^+kI~`y0oX4dqfh=HTFvJS delta 26686 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�~sC?B7bOq*ZQnOAs9{zphM9V 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 R2G;u_s^gcqCqG1={C{hxH0l5V diff --git a/lib/src/component/config_store.rs b/lib/src/component/config_store.rs index 439079a1..d807c901 100644 --- a/lib/src/component/config_store.rs +++ b/lib/src/component/config_store.rs @@ -1,6 +1,6 @@ use { super::fastly::api::{config_store, types}, - crate::{error, session::Session}, + crate::session::Session, }; #[async_trait::async_trait] @@ -25,11 +25,7 @@ impl config_store::Host for Session { }; if item.len() > usize::try_from(max_len).unwrap() { - return Err(error::Error::BufferLengthError { - buf: "item_out", - len: "item_max_len", - } - .into()); + return Err(types::Error::BufferLen(u64::try_from(item.len()).unwrap())); } Ok(Some(item.clone())) diff --git a/lib/src/component/device_detection.rs b/lib/src/component/device_detection.rs new file mode 100644 index 00000000..f7fa7efc --- /dev/null +++ b/lib/src/component/device_detection.rs @@ -0,0 +1,25 @@ +use { + super::fastly::api::{device_detection, types}, + crate::session::Session, +}; + +#[async_trait::async_trait] +impl device_detection::Host for Session { + async fn lookup( + &mut self, + user_agent: String, + max_len: u64, + ) -> Result, types::Error> { + if let Some(result) = self.device_detection_lookup(&user_agent) { + if result.len() > max_len as usize { + return Err(types::Error::BufferLen( + u64::try_from(result.len()).unwrap_or(0), + )); + } + + Ok(Some(result)) + } else { + Ok(None) + } + } +} diff --git a/lib/src/component/dictionary.rs b/lib/src/component/dictionary.rs index 41205303..9dfbc5ca 100644 --- a/lib/src/component/dictionary.rs +++ b/lib/src/component/dictionary.rs @@ -1,6 +1,6 @@ use { super::fastly::api::{dictionary, types}, - crate::{error, session::Session}, + crate::session::Session, }; #[async_trait::async_trait] @@ -25,11 +25,7 @@ impl dictionary::Host for Session { }; if item.len() > usize::try_from(max_len).unwrap() { - return Err(error::Error::BufferLengthError { - buf: "item_out", - len: "item_max_len", - } - .into()); + return Err(types::Error::BufferLen(u64::try_from(item.len()).unwrap())); } Ok(Some(item.clone())) diff --git a/lib/src/component/erl.rs b/lib/src/component/erl.rs new file mode 100644 index 00000000..9667197e --- /dev/null +++ b/lib/src/component/erl.rs @@ -0,0 +1,60 @@ +use { + super::fastly::api::{erl, types}, + crate::session::Session, +}; + +#[async_trait::async_trait] +impl erl::Host for Session { + async fn check_rate( + &mut self, + _rc: String, + _entry: String, + _delta: u32, + _window: u32, + _limit: u32, + _pb: String, + _ttl: u32, + ) -> Result { + Ok(0) + } + + async fn ratecounter_increment( + &mut self, + _rc: String, + _entry: String, + _delta: u32, + ) -> Result<(), types::Error> { + Ok(()) + } + + async fn ratecounter_lookup_rate( + &mut self, + _rc: String, + _entry: String, + _window: u32, + ) -> Result { + Ok(0) + } + + async fn ratecounter_lookup_count( + &mut self, + _rc: String, + _entry: String, + _duration: u32, + ) -> Result { + Ok(0) + } + + async fn penaltybox_add( + &mut self, + _pb: String, + _entry: String, + _ttl: u32, + ) -> Result<(), types::Error> { + Ok(()) + } + + async fn penaltybox_has(&mut self, _pb: String, _entry: String) -> Result { + Ok(0) + } +} diff --git a/lib/src/component/http_req.rs b/lib/src/component/http_req.rs index 0589c5fc..a477428b 100644 --- a/lib/src/component/http_req.rs +++ b/lib/src/component/http_req.rs @@ -384,7 +384,7 @@ impl http_req::Host for Session { let req = Request::from_parts(req_parts, req_body); let backend = self .backend(&backend_name) - .ok_or(types::Error::from(types::Error::UnknownError))?; + .ok_or_else(|| Error::UnknownBackend(backend_name))?; // synchronously send the request let resp = upstream::send_request(req, backend, self.tls_config()).await?; @@ -637,16 +637,20 @@ impl http_req::Host for Session { options: http_types::BackendConfigOptions, config: http_types::DynamicBackendConfig, ) -> Result<(), types::Error> { + if options.contains(http_types::BackendConfigOptions::RESERVED) { + return Err(types::Error::InvalidArgument); + } + let name = prefix.as_str(); let origin_name = target.as_str(); let override_host = if options.contains(http_types::BackendConfigOptions::HOST_OVERRIDE) { if config.host_override.is_empty() { - return Err(types::Error::InvalidArgument.into()); + return Err(types::Error::InvalidArgument); } if config.host_override.len() > 1024 { - return Err(types::Error::InvalidArgument.into()); + return Err(types::Error::InvalidArgument); } Some(HeaderValue::from_bytes(config.host_override.as_bytes())?) @@ -654,10 +658,25 @@ impl http_req::Host for Session { None }; - let scheme = if options.contains(http_types::BackendConfigOptions::USE_SSL) { - "https" + let use_ssl = options.contains(http_types::BackendConfigOptions::USE_SSL); + let scheme = if use_ssl { "https" } else { "http" }; + + let ca_certs = if use_ssl && options.contains(http_types::BackendConfigOptions::CA_CERT) { + if config.ca_cert.is_empty() { + return Err(types::Error::InvalidArgument); + } + + if config.ca_cert.len() > (64 * 1024) { + return Err(types::Error::InvalidArgument); + } + + let mut byte_cursor = std::io::Cursor::new(config.ca_cert.as_bytes()); + rustls_pemfile::certs(&mut byte_cursor)? + .drain(..) + .map(rustls::Certificate) + .collect() } else { - "http" + vec![] }; let mut cert_host = if options.contains(http_types::BackendConfigOptions::CERT_HOSTNAME) { @@ -675,12 +694,10 @@ impl http_req::Host for Session { }; let use_sni = if options.contains(http_types::BackendConfigOptions::SNI_HOSTNAME) { - if config.sni_hostname.len() > 1024 { - return Err(types::Error::InvalidArgument.into()); - } - if config.sni_hostname.is_empty() { false + } else if config.sni_hostname.len() > 1024 { + return Err(types::Error::InvalidArgument.into()); } else { if let Some(cert_host) = &cert_host { if cert_host != &config.sni_hostname { @@ -727,7 +744,7 @@ impl http_req::Host for Session { None }; - let grpc = false; + let grpc = options.contains(http_types::BackendConfigOptions::GRPC); let new_backend = Backend { uri: Uri::builder() @@ -740,7 +757,7 @@ impl http_req::Host for Session { use_sni, grpc, client_cert, - ca_certs: Vec::new(), + ca_certs, }; if !self.add_backend(name, new_backend) { @@ -789,10 +806,26 @@ impl http_req::Host for Session { async fn original_header_names_get( &mut self, - _max_len: u64, - _cursor: u32, + max_len: u64, + cursor: u32, ) -> Result, Option)>, types::Error> { - Err(Error::NotAvailable("Client Compliance Region").into()) + let headers = self.downstream_original_headers(); + let (buf, next) = write_values( + headers.keys(), + b'\0', + usize::try_from(max_len).unwrap(), + cursor, + ) + .map_err(|needed| types::Error::BufferLen(u64::try_from(needed).unwrap_or(0)))?; + + // At this point we know that the buffer being empty will also mean that there are no + // remaining entries to read. + if buf.is_empty() { + debug_assert!(next.is_none()); + Ok(None) + } else { + Ok(Some((buf, next))) + } } async fn original_header_count(&mut self) -> Result { diff --git a/lib/src/component/mod.rs b/lib/src/component/mod.rs index 833a0295..56c5a6e1 100644 --- a/lib/src/component/mod.rs +++ b/lib/src/component/mod.rs @@ -44,7 +44,9 @@ pub fn link_host_functions(linker: &mut component::Linker) -> anyh fastly::api::async_io::add_to_linker(linker, |x| x.session())?; fastly::api::backend::add_to_linker(linker, |x| x.session())?; fastly::api::cache::add_to_linker(linker, |x| x.session())?; + fastly::api::device_detection::add_to_linker(linker, |x| x.session())?; fastly::api::dictionary::add_to_linker(linker, |x| x.session())?; + fastly::api::erl::add_to_linker(linker, |x| x.session())?; fastly::api::geo::add_to_linker(linker, |x| x.session())?; fastly::api::http_body::add_to_linker(linker, |x| x.session())?; fastly::api::http_req::add_to_linker(linker, |x| x.session())?; @@ -65,7 +67,9 @@ pub mod async_io; pub mod backend; pub mod cache; pub mod config_store; +pub mod device_detection; pub mod dictionary; +pub mod erl; pub mod error; pub mod geo; pub mod headers; diff --git a/lib/wit/deps/fastly/compute.wit b/lib/wit/deps/fastly/compute.wit index 1f865a33..3a47e8f5 100644 --- a/lib/wit/deps/fastly/compute.wit +++ b/lib/wit/deps/fastly/compute.wit @@ -580,7 +580,7 @@ interface geo { interface device-detection { use types.{error}; - lookup: func(user-agent: string, max-len: u64) -> result; + lookup: func(user-agent: string, max-len: u64) -> result, error>; } /*