Merge pull request #107 from radu-matei/integration-tests

This commit is contained in:
Radu Matei 2022-03-01 17:32:53 +02:00 committed by GitHub
commit 6651c2ca8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 1080 additions and 4 deletions

1
Cargo.lock generated
View File

@ -2841,6 +2841,7 @@ dependencies = [
"dunce",
"env_logger",
"futures",
"hyper",
"path-absolutize",
"semver",
"serde",

View File

@ -31,6 +31,9 @@ tracing = { version = "0.1", features = [ "log" ] }
tracing-futures = "0.2"
tracing-subscriber = { version = "0.3.7", features = [ "env-filter" ] }
[dev-dependencies]
hyper = { version = "0.14", features = [ "full" ] }
[workspace]
members = [ "crates/config", "crates/engine", "crates/http", "crates/loader", "crates/templates" ]

View File

@ -8,9 +8,11 @@ const HTTP_TEST: &str = "crates/http/tests/rust-http-test";
const REDIS_WIT: &str = "crates/redis/wit/spin_redis_trigger_v01.wit";
const REDIS_TEST_RUST: &str = "crates/redis/tests/rust";
const WAGI_TEST: &str = "crates/http/tests/wagi-test";
const RUST_HTTP_INTEGRATION_TEST: &str = "tests/http/simple-spin-rust";
const RUST_HTTP_INTEGRATION_ENV_TEST: &str = "tests/http/headers-env-routes-test";
fn main() {
println!("cargo:rerun-if-changed=build.rs");
@ -25,6 +27,9 @@ fn main() {
cargo_build(HTTP_TEST);
cargo_build(REDIS_TEST_RUST);
cargo_build(WAGI_TEST);
cargo_build(RUST_HTTP_INTEGRATION_TEST);
cargo_build(RUST_HTTP_INTEGRATION_ENV_TEST);
}
fn cargo_build(dir: &str) {

View File

@ -1,6 +1,6 @@
apiVersion = "0.1.0"
authors = ["Radu Matei <radu@fermyon.com>"]
description = "A simple application that returns hello and goodbye."
authors = ["Fermyon Engineering <engineering@fermyon.com>"]
description = "A simple application that returns hello."
name = "spin-hello-world"
trigger = {type = "http", base = "/test"}
version = "1.0.0"

View File

@ -4,7 +4,7 @@ use spin_http::{Request, Response};
// Generate Rust bindings for interface defined in spin-http.wit file
wit_bindgen_rust::export!("spin-http.wit");
struct SpinHttp {}
struct SpinHttp;
impl spin_http::SpinHttp for SpinHttp {
// Implement the `handler` entrypoint for Spin HTTP components.
fn handler(req: Request) -> Response {

View File

@ -0,0 +1,14 @@
apiVersion = "0.1.0"
authors = ["Fermyon Engineering <engineering@fermyon.com>"]
name = "spin-assets-test"
trigger = {type = "http", base = "/"}
version = "1.0.0"
[[component]]
id = "fs"
source = "spin_static_fs.wasm"
files = ["static/thisshouldbemounted/*"]
environment = { PATH_PREFIX = "static/" }
[component.trigger]
executor = {type = "spin"}
route = "/static/..."

Binary file not shown.

View File

@ -0,0 +1 @@
a

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1 @@
2

View File

@ -0,0 +1 @@
3

View File

@ -0,0 +1,206 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "anyhow"
version = "1.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3"
[[package]]
name = "async-trait"
version = "0.1.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "env"
version = "0.1.0"
dependencies = [
"wit-bindgen-rust",
]
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "id-arena"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
[[package]]
name = "memchr"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "proc-macro2"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [
"unicode-xid",
]
[[package]]
name = "pulldown-cmark"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8"
dependencies = [
"bitflags",
"memchr",
"unicase",
]
[[package]]
name = "quote"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "tinyvec"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "unicase"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
dependencies = [
"version_check",
]
[[package]]
name = "unicode-normalization"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-segmentation"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wit-bindgen-gen-core"
version = "0.1.0"
source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=e9c7c0a3405845cecd3fe06f3c20ab413302fc73#e9c7c0a3405845cecd3fe06f3c20ab413302fc73"
dependencies = [
"anyhow",
"wit-parser",
]
[[package]]
name = "wit-bindgen-gen-rust"
version = "0.1.0"
source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=e9c7c0a3405845cecd3fe06f3c20ab413302fc73#e9c7c0a3405845cecd3fe06f3c20ab413302fc73"
dependencies = [
"heck",
"wit-bindgen-gen-core",
]
[[package]]
name = "wit-bindgen-gen-rust-wasm"
version = "0.1.0"
source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=e9c7c0a3405845cecd3fe06f3c20ab413302fc73#e9c7c0a3405845cecd3fe06f3c20ab413302fc73"
dependencies = [
"heck",
"wit-bindgen-gen-core",
"wit-bindgen-gen-rust",
]
[[package]]
name = "wit-bindgen-rust"
version = "0.1.0"
source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=e9c7c0a3405845cecd3fe06f3c20ab413302fc73#e9c7c0a3405845cecd3fe06f3c20ab413302fc73"
dependencies = [
"async-trait",
"bitflags",
"wit-bindgen-rust-impl",
]
[[package]]
name = "wit-bindgen-rust-impl"
version = "0.1.0"
source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=e9c7c0a3405845cecd3fe06f3c20ab413302fc73#e9c7c0a3405845cecd3fe06f3c20ab413302fc73"
dependencies = [
"proc-macro2",
"syn",
"wit-bindgen-gen-core",
"wit-bindgen-gen-rust-wasm",
]
[[package]]
name = "wit-parser"
version = "0.1.0"
source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=e9c7c0a3405845cecd3fe06f3c20ab413302fc73#e9c7c0a3405845cecd3fe06f3c20ab413302fc73"
dependencies = [
"anyhow",
"id-arena",
"pulldown-cmark",
"unicode-normalization",
"unicode-xid",
]

View File

@ -0,0 +1,17 @@
[package]
name = "env"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = [ "cdylib" ]
[dependencies]
# The wit-bindgen-rust dependency generates bindings for interfaces.
wit-bindgen-rust = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "e9c7c0a3405845cecd3fe06f3c20ab413302fc73" }
[workspace]
# Metadata about this component.
[package.metadata.component]
name = "spinhelloworld"

View File

@ -0,0 +1,61 @@
// The entrypoint for an HTTP handler.
handler: function(req: request) -> response
// This is a temporary workaround very similar to https://github.com/deislabs/wasi-experimental-http.
// Once asynchronous functions, streams, and the upstream HTTP API are available, this should be removed.
// The HTTP status code.
// This is currently an unsigned 16-bit integer,
// but it could be represented as an enum containing
// all possible HTTP status codes.
type http-status = u16
// The HTTP body.
// Currently, this is a synchonous byte array, but it should be
// possible to have a stream for both request and response bodies.
type body = list<u8>
// The HTTP headers represented as a list of (name, value) pairs.
type headers = list<tuple<string, string>>
// The HTTP parameter queries, represented as a list of (name, value) pairs.
type params = list<tuple<string, string>>
// The HTTP URI of the current request.
type uri = string
// The HTTP method.
enum method {
get,
post,
put,
delete,
patch,
head,
options,
}
// An HTTP request.
record request {
method: method,
uri: uri,
headers: headers,
params: params,
body: option<body>,
}
// An HTTP response.
record response {
status: http-status,
headers: option<headers>,
body: option<body>,
}
// HTTP errors returned by the runtime.
enum http-error {
success,
destination-not-allowed,
invalid-url,
request-error,
runtime-error,
}

View File

@ -0,0 +1,13 @@
apiVersion = "0.1.0"
name = "spin-headers-env-routes-test"
version = "1.0.0"
authors = ["Fermyon Engineering <engineering@fermyon.com>"]
description = "A simple application that returns hello and goodbye."
trigger = {type = "http", base = "/"}
[[component]]
id = "env"
source = "target/wasm32-wasi/release/env.wasm"
environment = { some_key = "some_value" }
[component.trigger]
route = "/env/..."

View File

@ -0,0 +1,41 @@
// Import the HTTP objects from the generated bindings.
use spin_http::{Request, Response};
// Generate Rust bindings for interface defined in spin-http.wit file
wit_bindgen_rust::export!("spin-http.wit");
struct SpinHttp;
impl spin_http::SpinHttp for SpinHttp {
// Implement the `handler` entrypoint for Spin HTTP components.
// This handler does the following:
// - returns all environment variables as headers with an ENV_ prefix
// - returns all request headers as response headers.
fn handler(req: Request) -> Response {
let mut headers = Self::env_to_headers();
Self::append_request_headers(&mut headers, &req.headers);
let headers = Some(headers);
Response {
status: 200,
headers,
body: Some("I'm a teapot".as_bytes().to_vec()),
}
}
}
impl SpinHttp {
fn env_to_headers() -> Vec<(String, String)> {
let mut res = vec![];
std::env::vars().for_each(|(k, v)| res.push((format!("ENV_{}", k), v)));
res
}
fn append_request_headers(
res_headers: &mut Vec<(String, String)>,
req_headers: &[(String, String)],
) {
for (k, v) in req_headers {
res_headers.push((k.clone(), v.clone()));
}
}
}

View File

@ -0,0 +1,2 @@
[build]
target = "wasm32-wasi"

206
tests/http/simple-spin-rust/Cargo.lock generated Normal file
View File

@ -0,0 +1,206 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "anyhow"
version = "1.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3"
[[package]]
name = "async-trait"
version = "0.1.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "id-arena"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
[[package]]
name = "memchr"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "proc-macro2"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [
"unicode-xid",
]
[[package]]
name = "pulldown-cmark"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8"
dependencies = [
"bitflags",
"memchr",
"unicase",
]
[[package]]
name = "quote"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "spinhelloworld"
version = "0.1.0"
dependencies = [
"wit-bindgen-rust",
]
[[package]]
name = "syn"
version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "tinyvec"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "unicase"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
dependencies = [
"version_check",
]
[[package]]
name = "unicode-normalization"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-segmentation"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wit-bindgen-gen-core"
version = "0.1.0"
source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=e9c7c0a3405845cecd3fe06f3c20ab413302fc73#e9c7c0a3405845cecd3fe06f3c20ab413302fc73"
dependencies = [
"anyhow",
"wit-parser",
]
[[package]]
name = "wit-bindgen-gen-rust"
version = "0.1.0"
source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=e9c7c0a3405845cecd3fe06f3c20ab413302fc73#e9c7c0a3405845cecd3fe06f3c20ab413302fc73"
dependencies = [
"heck",
"wit-bindgen-gen-core",
]
[[package]]
name = "wit-bindgen-gen-rust-wasm"
version = "0.1.0"
source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=e9c7c0a3405845cecd3fe06f3c20ab413302fc73#e9c7c0a3405845cecd3fe06f3c20ab413302fc73"
dependencies = [
"heck",
"wit-bindgen-gen-core",
"wit-bindgen-gen-rust",
]
[[package]]
name = "wit-bindgen-rust"
version = "0.1.0"
source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=e9c7c0a3405845cecd3fe06f3c20ab413302fc73#e9c7c0a3405845cecd3fe06f3c20ab413302fc73"
dependencies = [
"async-trait",
"bitflags",
"wit-bindgen-rust-impl",
]
[[package]]
name = "wit-bindgen-rust-impl"
version = "0.1.0"
source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=e9c7c0a3405845cecd3fe06f3c20ab413302fc73#e9c7c0a3405845cecd3fe06f3c20ab413302fc73"
dependencies = [
"proc-macro2",
"syn",
"wit-bindgen-gen-core",
"wit-bindgen-gen-rust-wasm",
]
[[package]]
name = "wit-parser"
version = "0.1.0"
source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=e9c7c0a3405845cecd3fe06f3c20ab413302fc73#e9c7c0a3405845cecd3fe06f3c20ab413302fc73"
dependencies = [
"anyhow",
"id-arena",
"pulldown-cmark",
"unicode-normalization",
"unicode-xid",
]

View File

@ -0,0 +1,17 @@
[package]
name = "spinhelloworld"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = [ "cdylib" ]
[dependencies]
# The wit-bindgen-rust dependency generates bindings for interfaces.
wit-bindgen-rust = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "e9c7c0a3405845cecd3fe06f3c20ab413302fc73" }
[workspace]
# Metadata about this component.
[package.metadata.component]
name = "spinhelloworld"

View File

@ -0,0 +1,61 @@
// The entrypoint for an HTTP handler.
handler: function(req: request) -> response
// This is a temporary workaround very similar to https://github.com/deislabs/wasi-experimental-http.
// Once asynchronous functions, streams, and the upstream HTTP API are available, this should be removed.
// The HTTP status code.
// This is currently an unsigned 16-bit integer,
// but it could be represented as an enum containing
// all possible HTTP status codes.
type http-status = u16
// The HTTP body.
// Currently, this is a synchonous byte array, but it should be
// possible to have a stream for both request and response bodies.
type body = list<u8>
// The HTTP headers represented as a list of (name, value) pairs.
type headers = list<tuple<string, string>>
// The HTTP parameter queries, represented as a list of (name, value) pairs.
type params = list<tuple<string, string>>
// The HTTP URI of the current request.
type uri = string
// The HTTP method.
enum method {
get,
post,
put,
delete,
patch,
head,
options,
}
// An HTTP request.
record request {
method: method,
uri: uri,
headers: headers,
params: params,
body: option<body>,
}
// An HTTP response.
record response {
status: http-status,
headers: option<headers>,
body: option<body>,
}
// HTTP errors returned by the runtime.
enum http-error {
success,
destination-not-allowed,
invalid-url,
request-error,
runtime-error,
}

View File

@ -0,0 +1,12 @@
apiVersion = "0.1.0"
authors = ["Fermyon Engineering <engineering@fermyon.com>"]
description = "A simple application that returns hello and goodbye."
name = "spin-hello-world"
trigger = {type = "http", base = "/test"}
version = "1.0.0"
[[component]]
id = "hello"
source = "target/wasm32-wasi/release/spinhelloworld.wasm"
[component.trigger]
route = "/hello/..."

View File

@ -0,0 +1,17 @@
// Import the HTTP objects from the generated bindings.
use spin_http::{Request, Response};
// Generate Rust bindings for interface defined in spin-http.wit file
wit_bindgen_rust::export!("spin-http.wit");
struct SpinHttp {}
impl spin_http::SpinHttp for SpinHttp {
// Implement the `handler` entrypoint for Spin HTTP components.
fn handler(req: Request) -> Response {
Response {
status: 200,
headers: None,
body: Some("I'm a teapot".as_bytes().to_vec()),
}
}
}

396
tests/integration.rs Normal file
View File

@ -0,0 +1,396 @@
#[cfg(test)]
mod integration_tests {
use anyhow::{Context, Result};
use hyper::{header::HeaderName, Body, Client, Response};
use std::{
ffi::OsStr,
net::{Ipv4Addr, SocketAddrV4, TcpListener},
process::{self, Child, Command},
time::Duration,
};
use tempfile::TempDir;
use tokio::{net::TcpStream, time::sleep};
const RUST_HTTP_INTEGRATION_TEST: &str = "tests/http/simple-spin-rust";
const RUST_HTTP_INTEGRATION_TEST_REF: &str = "spin-hello-world/1.0.0";
const RUST_HTTP_STATIC_ASSETS_TEST: &str = "tests/http/assets-test";
const RUST_HTTP_STATIC_ASSETS_REST_REF: &str = "spin-assets-test/1.0.0";
const RUST_HTTP_HEADERS_ENV_ROUTES_TEST: &str = "tests/http/headers-env-routes-test";
const RUST_HTTP_HEADERS_ENV_ROUTES_TEST_REF: &str = "spin-headers-env-routes-test/1.0.0";
const DEFAULT_MANIFEST_LOCATION: &str = "spin.toml";
const SPIN_BINARY: &str = "./target/debug/spin";
const BINDLE_SERVER_BINARY: &str = "bindle-server";
// This assumes all tests have been previously compiled by the top-level build script.
#[tokio::test]
async fn test_simple_rust_local() -> Result<()> {
let s = SpinTestController::with_manifest(
&format!(
"{}/{}",
RUST_HTTP_INTEGRATION_TEST, DEFAULT_MANIFEST_LOCATION
),
&[],
)
.await?;
assert_status(&s, "/test/hello", 200).await?;
assert_status(&s, "/test/hello/wildcards/should/be/handled", 200).await?;
assert_status(&s, "/thisshouldfail", 404).await?;
Ok(())
}
#[tokio::test]
async fn test_bindle_roundtrip() -> Result<()> {
// start the Bindle registry.
let b = BindleTestController::new().await?;
// push the application to the registry using the Spin CLI.
run(
vec![
SPIN_BINARY,
"bindle",
"push",
"--file",
&format!(
"{}/{}",
RUST_HTTP_INTEGRATION_TEST, DEFAULT_MANIFEST_LOCATION
),
"--bindle-server",
&b.url,
],
None,
)?;
// start Spin using the bindle reference of the application that was just pushed.
let s =
SpinTestController::with_bindle(RUST_HTTP_INTEGRATION_TEST_REF, &b.url, &[]).await?;
assert_status(&s, "/test/hello", 200).await?;
assert_status(&s, "/test/hello/wildcards/should/be/handled", 200).await?;
assert_status(&s, "/thisshouldfail", 404).await?;
Ok(())
}
#[tokio::test]
async fn test_bindle_static_assets() -> Result<()> {
// start the Bindle registry.
let b = BindleTestController::new().await?;
// push the application to the registry using the Spin CLI.
run(
vec![
SPIN_BINARY,
"bindle",
"push",
"--file",
&format!(
"{}/{}",
RUST_HTTP_STATIC_ASSETS_TEST, DEFAULT_MANIFEST_LOCATION
),
"--bindle-server",
&b.url,
],
None,
)?;
// start Spin using the bindle reference of the application that was just pushed.
let s =
SpinTestController::with_bindle(RUST_HTTP_STATIC_ASSETS_REST_REF, &b.url, &[]).await?;
assert_status(&s, "/static/thisshouldbemounted/1", 200).await?;
assert_status(&s, "/static/thisshouldbemounted/2", 200).await?;
assert_status(&s, "/static/thisshouldbemounted/3", 200).await?;
assert_status(&s, "/static/donotmount/a", 404).await?;
Ok(())
}
#[tokio::test]
async fn test_headers_env_routes() -> Result<()> {
// start the Bindle registry.
let b = BindleTestController::new().await?;
// push the application to the registry using the Spin CLI.
run(
vec![
SPIN_BINARY,
"bindle",
"push",
"--file",
&format!(
"{}/{}",
RUST_HTTP_HEADERS_ENV_ROUTES_TEST, DEFAULT_MANIFEST_LOCATION
),
"--bindle-server",
&b.url,
],
None,
)?;
// start Spin using the bindle reference of the application that was just pushed.
let s = SpinTestController::with_bindle(
RUST_HTTP_HEADERS_ENV_ROUTES_TEST_REF,
&b.url,
&["foo=bar"],
)
.await?;
assert_status(&s, "/env", 200).await?;
verify_headers(
&s,
"/env/foo",
200,
&[("env_foo", "bar"), ("env_some_key", "some_value")],
"/foo",
)
.await?;
Ok(())
}
async fn verify_headers(
s: &SpinTestController,
absolute_uri: &str,
expected: u16,
expected_env_as_headers: &[(&str, &str)],
expected_path_info: &str,
) -> Result<()> {
let res = req(s, absolute_uri).await?;
assert_eq!(res.status(), expected);
// check the environment variables sent back as headers:
for (k, v) in expected_env_as_headers {
assert_eq!(
&res.headers()
.get(HeaderName::from_bytes(k.as_bytes())?)
.unwrap_or_else(|| panic!("cannot find header {}", k))
.to_str()?,
v
);
}
assert_eq!(
res.headers()
.get(HeaderName::from_bytes("PATH_INFO".as_bytes())?)
.unwrap_or_else(|| panic!("cannot find PATH_INFO header"))
.to_str()?,
expected_path_info
);
Ok(())
}
async fn assert_status(
s: &SpinTestController,
absolute_uri: &str,
expected: u16,
) -> Result<()> {
let res = req(s, absolute_uri).await?;
assert_eq!(res.status(), expected);
Ok(())
}
async fn req(s: &SpinTestController, absolute_uri: &str) -> Result<Response<Body>> {
let c = Client::new();
let url = format!("http://{}{}", s.url, absolute_uri)
.parse()
.with_context(|| "cannot parse URL")?;
Ok(c.get(url).await?)
}
/// Controller for running Spin.
pub struct SpinTestController {
pub url: String,
spin_handle: Child,
}
impl SpinTestController {
pub async fn with_manifest(
manifest_path: &str,
env: &[&str],
) -> Result<SpinTestController> {
// start Spin using the given application manifest and wait for the HTTP server to be available.
let url = format!("127.0.0.1:{}", get_random_port()?);
let mut args = vec!["up", "--file", manifest_path, "--listen", &url];
for v in env {
args.push("--env");
args.push(v);
}
let spin_handle = Command::new(get_process(SPIN_BINARY))
.args(args)
.env(
"RUST_LOG",
"spin=trace,spin_loader=trace,spin_engine=trace,spin_http=trace",
)
.spawn()
.with_context(|| "executing Spin")?;
// ensure the server is accepting requests before continuing.
wait_tcp(&url, SPIN_BINARY).await?;
Ok(SpinTestController { url, spin_handle })
}
// Unfortunately, this is a lot of duplicated code.
pub async fn with_bindle(
id: &str,
bindle_url: &str,
env: &[&str],
) -> Result<SpinTestController> {
let url = format!("127.0.0.1:{}", get_random_port()?);
let mut args = vec![
"up", "--bindle", id, "--server", bindle_url, "--listen", &url,
];
for v in env {
args.push("--env");
args.push(v);
}
let spin_handle = Command::new(get_process(SPIN_BINARY))
.args(args)
.env(
"RUST_LOG",
"spin=trace,spin_loader=trace,spin_engine=trace,spin_http=trace",
)
.spawn()
.with_context(|| "executing Spin")?;
// ensure the server is accepting requests before continuing.
wait_tcp(&url, SPIN_BINARY).await?;
Ok(SpinTestController { url, spin_handle })
}
}
impl Drop for SpinTestController {
fn drop(&mut self) {
let _ = self.spin_handle.kill();
}
}
/// Controller for running a Bindle server.
/// This assumes `bindle-server` is present in the path.
pub struct BindleTestController {
pub url: String,
pub server_cache: TempDir,
server_handle: Child,
}
impl BindleTestController {
pub async fn new() -> Result<BindleTestController> {
let server_cache = tempfile::tempdir()?;
let address = format!("127.0.0.1:{}", get_random_port()?);
let url = format!("http://{}/v1/", address);
let server_handle = Command::new(BINDLE_SERVER_BINARY)
.args(&[
"-d",
server_cache.path().to_string_lossy().to_string().as_str(),
"-i",
address.as_str(),
"--unauthenticated",
])
.spawn()
.with_context(|| format!("executing {}", BINDLE_SERVER_BINARY))?;
wait_tcp(&address, BINDLE_SERVER_BINARY).await?;
Ok(Self {
url,
server_handle,
server_cache,
})
}
}
impl Drop for BindleTestController {
fn drop(&mut self) {
let _ = self.server_handle.kill();
}
}
fn run<S: Into<String> + AsRef<OsStr>>(args: Vec<S>, dir: Option<S>) -> Result<()> {
let mut cmd = Command::new(get_os_process());
cmd.stdout(process::Stdio::piped());
cmd.stderr(process::Stdio::piped());
if let Some(dir) = dir {
cmd.current_dir(dir.into());
};
cmd.arg("-c");
cmd.arg(
args.into_iter()
.map(Into::into)
.collect::<Vec<String>>()
.join(" "),
);
let output = cmd.output()?;
let code = output.status.code().expect("should have status code");
if code != 0 {
println!("{:#?}", std::str::from_utf8(&output.stderr)?);
println!("{:#?}", std::str::from_utf8(&output.stdout)?);
panic!("command `{:?}` exited with code {}", cmd, code);
}
Ok(())
}
fn get_process(binary: &str) -> String {
if cfg!(target_os = "windows") {
format!("{}.exe", binary)
} else {
binary.to_string()
}
}
fn get_os_process() -> String {
if cfg!(target_os = "windows") {
String::from("powershell.exe")
} else {
String::from("/bin/bash")
}
}
fn get_random_port() -> Result<u16> {
Ok(
TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0))?
.local_addr()?
.port(),
)
}
async fn wait_tcp(url: &str, target: &str) -> Result<()> {
let mut wait_count = 0;
loop {
if wait_count >= 50 {
panic!(
"Ran out of retries waiting for {} to start on URL {}",
target, url
);
}
match TcpStream::connect(&url).await {
Ok(_) => break,
Err(_) => {
wait_count += 1;
sleep(Duration::from_secs(1)).await;
}
}
}
Ok(())
}
}