Merge pull request #1459 from itowlson/test-spin-up-kv

Exercise `spin up --key-value` in tests
This commit is contained in:
itowlson 2023-05-11 08:22:44 +12:00 committed by GitHub
commit 8be5db2b93
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 69 additions and 13 deletions

1
Cargo.lock generated
View File

@ -6073,6 +6073,7 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79"
dependencies = [
"getrandom 0.2.8",
"serde",
]

View File

@ -67,7 +67,7 @@ toml = "0.6"
tracing = { workspace = true }
tracing-subscriber = { version = "0.3.7", features = ["env-filter"] }
url = "2.2.2"
uuid = "^1.0"
uuid = { version = "^1.0", features = ["v4"] }
wasmtime = { workspace = true }
watchexec = { git = "https://github.com/watchexec/watchexec.git", rev = "8e91d26ef6400c1e60b32a8314cbb144fa33f288" }
subprocess = "0.2.9"

View File

@ -10,11 +10,13 @@ use std::future::Future;
use tokio::io::BufReader;
use tokio::process::{ChildStderr, ChildStdout};
type ChecksFunc = fn(
AppMetadata,
Option<BufReader<ChildStdout>>,
Option<BufReader<ChildStderr>>,
) -> Pin<Box<dyn Future<Output = Result<()>>>>;
type ChecksFunc = Box<
dyn Fn(
AppMetadata,
Option<BufReader<ChildStdout>>,
Option<BufReader<ChildStderr>>,
) -> Pin<Box<dyn Future<Output = Result<()>>>>,
>;
/// Represents a testcase
#[derive(Builder)]
@ -65,6 +67,7 @@ pub struct TestCase {
pub pre_build_hooks: Option<Vec<Vec<String>>>,
/// assertions to run once the app is running
#[builder(setter(custom))]
pub assertions: ChecksFunc,
/// registry app url where app is pushed and run from
@ -72,6 +75,22 @@ pub struct TestCase {
pub push_to_registry: Option<String>,
}
impl TestCaseBuilder {
pub fn assertions(
self,
value: impl Fn(
AppMetadata,
Option<BufReader<ChildStdout>>,
Option<BufReader<ChildStderr>>,
) -> Pin<Box<dyn Future<Output = Result<()>>>>
+ 'static,
) -> Self {
let mut this = self;
this.assertions = Some(Box::new(value));
this
}
}
impl TestCase {
pub async fn run(&self, controller: &dyn Controller) -> Result<()> {
controller.name();

View File

@ -13,6 +13,9 @@ anyhow = "1"
bytes = "1"
# General-purpose crate with common HTTP types.
http = "0.2"
itertools = "0.10"
serde = { version = "1.0", features = ["derive"] }
serde_qs = "0.12"
spin-sdk = { path = "../../../sdk/rust"}
# The wit-bindgen-rust dependency generates bindings for interfaces.
wit-bindgen-rust = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "cb871cfa1ee460b51eb1d144b175b9aab9c50aba" }

View File

@ -1,4 +1,5 @@
use anyhow::{ensure, Result};
use itertools::sorted;
use spin_sdk::{
http::{Request, Response},
http_component,
@ -6,11 +7,16 @@ use spin_sdk::{
};
#[http_component]
fn handle_request(_req: Request) -> Result<Response> {
fn handle_request(req: Request) -> Result<Response> {
// TODO: once we allow users to pass non-default stores, test that opening
// an allowed-but-non-existent one returns Error::NoSuchStore
ensure!(matches!(Store::open("forbidden"), Err(Error::AccessDenied)));
let query = req.uri().query().expect("Should have a testkey query string");
let query: std::collections::HashMap::<String, String> = serde_qs::from_str(query)?;
let init_key = query.get("testkey").expect("Should have a testkey query string");
let init_val = query.get("testval").expect("Should have a testval query string");
let store = Store::open_default()?;
store.delete("bar")?;
@ -29,9 +35,21 @@ fn handle_request(_req: Request) -> Result<Response> {
ensure!(b"wow" as &[_] == &store.get("bar")?);
ensure!(&["bar".to_owned()] as &[_] == &store.get_keys()?);
ensure!(
init_val.as_bytes() == &store.get(&init_key)?,
"Expected to look up {init_key} and get {init_val} but actually got {}",
String::from_utf8_lossy(&store.get(&init_key)?)
);
ensure!(
sorted(vec!["bar".to_owned(), init_key.to_owned()]).collect::<Vec<_>>() == sorted(store.get_keys()?).collect::<Vec<_>>(),
"Expected exectly keys 'bar' and '{}' but got '{:?}'",
init_key,
&store.get_keys()?
);
store.delete("bar")?;
store.delete(&init_key)?;
ensure!(&[] as &[String] == &store.get_keys()?);

View File

@ -20,13 +20,19 @@ pub mod all {
pub async fn key_value_works(controller: &dyn Controller) {
async fn checks(
metadata: AppMetadata,
test_init_key: String,
test_init_value: String,
// TODO: investigate why omitting these two next parameters does not
// cause a compile time error but causes a runtime one
_: Option<BufReader<ChildStdout>>,
_: Option<BufReader<ChildStderr>>,
) -> Result<()> {
assert_http_response(
get_url(metadata.base.as_str(), "/test").as_str(),
get_url(
metadata.base.as_str(),
&format!("/test?testkey={test_init_key}&testval={test_init_value}"),
)
.as_str(),
Method::GET,
"",
200,
@ -36,15 +42,24 @@ pub mod all {
.await
}
let init_key = uuid::Uuid::new_v4().to_string();
let init_value = uuid::Uuid::new_v4().to_string();
let tc = TestCaseBuilder::default()
.name("key-value".to_string())
.appname(Some("key-value".to_string()))
.template(None)
.deploy_args(vec![
"--key-value".to_string(),
format!("{init_key}={init_value}"),
])
.assertions(
|metadata: AppMetadata,
stdout_stream: Option<BufReader<ChildStdout>>,
stderr_stream: Option<BufReader<ChildStderr>>| {
Box::pin(checks(metadata, stdout_stream, stderr_stream))
move |metadata: AppMetadata,
stdout_stream: Option<BufReader<ChildStdout>>,
stderr_stream: Option<BufReader<ChildStderr>>| {
let ik = init_key.clone();
let iv = init_value.clone();
Box::pin(checks(metadata, ik, iv, stdout_stream, stderr_stream))
},
)
.build()