diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9887529a8..6c7528d18 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -170,6 +170,8 @@ jobs: - name: Run Integration Tests run: | make test-integration + env: + SPIN_CONFORMANCE_TESTS_DOCKER_OPT_OUT: true # Only run integration tests on macOS as they will be run on ubuntu separately if: ${{ matrix.runner == 'macos-14' }} diff --git a/Cargo.lock b/Cargo.lock index f486b09f8..39e004a7e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7347,6 +7347,8 @@ dependencies = [ "clearscreen", "comfy-table", "command-group", + "conformance", + "conformance-tests", "ctrlc", "dialoguer 0.10.4", "dirs 4.0.0", diff --git a/Cargo.toml b/Cargo.toml index eeef23326..8497b1e1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,6 +100,8 @@ runtime-tests = { path = "tests/runtime-tests" } test-components = { path = "tests/test-components" } test-codegen-macro = { path = "crates/test-codegen-macro" } test-environment = { git = "https://github.com/fermyon/conformance-tests", rev = "d2129a3fd73140a76c77f15a030a5273b37cbd11" } +conformance-tests = { git = "https://github.com/fermyon/conformance-tests", rev = "d2129a3fd73140a76c77f15a030a5273b37cbd11" } +conformance = { path = "tests/conformance-tests" } [build-dependencies] cargo-target-dep = { git = "https://github.com/fermyon/cargo-target-dep", rev = "482f269eceb7b1a7e8fc618bf8c082dd24979cf1" } diff --git a/tests/conformance-tests/src/lib.rs b/tests/conformance-tests/src/lib.rs new file mode 100644 index 000000000..a03084819 --- /dev/null +++ b/tests/conformance-tests/src/lib.rs @@ -0,0 +1,78 @@ +use anyhow::Context as _; +use testing_framework::runtimes::spin_cli::{SpinCli, SpinConfig}; + +/// Run a single conformance test against the supplied spin binary. +pub fn run_test( + test: conformance_tests::Test, + spin_binary: &std::path::Path, +) -> anyhow::Result<()> { + let mut services = Vec::new(); + for precondition in test.config.preconditions { + match precondition { + conformance_tests::config::Precondition::HttpEcho => { + services.push("http-echo"); + } + conformance_tests::config::Precondition::TcpEcho => { + services.push("tcp-echo"); + } + conformance_tests::config::Precondition::Redis => { + if should_run_docker_based_tests() { + services.push("redis") + } else { + // Skip the test if docker is not installed. + return Ok(()); + } + } + conformance_tests::config::Precondition::Mqtt => { + if should_run_docker_based_tests() { + services.push("mqtt") + } else { + // Skip the test if docker is not installed. + return Ok(()); + } + } + conformance_tests::config::Precondition::KeyValueStore(_) => {} + conformance_tests::config::Precondition::Sqlite => {} + } + } + let env_config = SpinCli::config( + SpinConfig { + binary_path: spin_binary.to_owned(), + spin_up_args: Vec::new(), + app_type: testing_framework::runtimes::SpinAppType::Http, + }, + test_environment::services::ServicesConfig::new(services)?, + move |e| { + let mut manifest = + test_environment::manifest_template::EnvTemplate::from_file(&test.manifest)?; + manifest.substitute(e, |_| None)?; + e.write_file("spin.toml", manifest.contents())?; + e.copy_into(&test.component, test.component.file_name().unwrap())?; + Ok(()) + }, + ); + let mut env = test_environment::TestEnvironment::up(env_config, |_| Ok(()))?; + for invocation in test.config.invocations { + let conformance_tests::config::Invocation::Http(mut invocation) = invocation; + invocation.request.substitute_from_env(&mut env)?; + let spin = env.runtime_mut(); + let actual = invocation + .request + .send(|request| spin.make_http_request(request))?; + + conformance_tests::assertions::assert_response(&invocation.response, &actual) + .with_context(|| { + format!( + "Failed assertion.\nstdout: {}\nstderr: {}", + spin.stdout().to_owned(), + spin.stderr() + ) + })?; + } + Ok(()) +} + +/// Whether or not docker is installed on the system. +fn should_run_docker_based_tests() -> bool { + std::env::var("SPIN_CONFORMANCE_TESTS_DOCKER_OPT_OUT").is_err() +} diff --git a/tests/conformance-tests/src/main.rs b/tests/conformance-tests/src/main.rs index 6c67433b9..38b97a6bd 100644 --- a/tests/conformance-tests/src/main.rs +++ b/tests/conformance-tests/src/main.rs @@ -1,63 +1,7 @@ -use anyhow::Context as _; -use testing_framework::runtimes::spin_cli::{SpinCli, SpinConfig}; - fn main() { let spin_binary: std::path::PathBuf = std::env::args() .nth(1) .expect("expected first argument to be path to spin binary") .into(); - conformance_tests::run_tests(move |test| run_test(test, &spin_binary)).unwrap(); -} - -fn run_test(test: conformance_tests::Test, spin_binary: &std::path::Path) -> anyhow::Result<()> { - let mut services = Vec::new(); - for precondition in test.config.preconditions { - match precondition { - conformance_tests::config::Precondition::HttpEcho => { - services.push("http-echo"); - } - conformance_tests::config::Precondition::TcpEcho => { - services.push("tcp-echo"); - } - conformance_tests::config::Precondition::Redis => services.push("redis"), - conformance_tests::config::Precondition::Mqtt => services.push("mqtt"), - conformance_tests::config::Precondition::KeyValueStore(_) => {} - conformance_tests::config::Precondition::Sqlite => {} - } - } - let env_config = SpinCli::config( - SpinConfig { - binary_path: spin_binary.to_owned(), - spin_up_args: Vec::new(), - app_type: testing_framework::runtimes::SpinAppType::Http, - }, - test_environment::services::ServicesConfig::new(services)?, - move |e| { - let mut manifest = - test_environment::manifest_template::EnvTemplate::from_file(&test.manifest)?; - manifest.substitute(e, |_| None)?; - e.write_file("spin.toml", manifest.contents())?; - e.copy_into(&test.component, test.component.file_name().unwrap())?; - Ok(()) - }, - ); - let mut env = test_environment::TestEnvironment::up(env_config, |_| Ok(()))?; - for invocation in test.config.invocations { - let conformance_tests::config::Invocation::Http(mut invocation) = invocation; - invocation.request.substitute_from_env(&mut env)?; - let spin = env.runtime_mut(); - let actual = invocation - .request - .send(|request| spin.make_http_request(request))?; - - conformance_tests::assertions::assert_response(&invocation.response, &actual) - .with_context(|| { - format!( - "Failed assertion.\nstdout: {}\nstderr: {}", - spin.stdout().to_owned(), - spin.stderr() - ) - })?; - } - Ok(()) + conformance_tests::run_tests(move |test| conformance::run_test(test, &spin_binary)).unwrap(); } diff --git a/tests/runtime-tests/tests/key-value-no-permission/error.txt b/tests/runtime-tests/tests/key-value-no-permission/error.txt deleted file mode 100644 index 11a87ef97..000000000 --- a/tests/runtime-tests/tests/key-value-no-permission/error.txt +++ /dev/null @@ -1 +0,0 @@ -Error::AccessDenied \ No newline at end of file diff --git a/tests/runtime-tests/tests/key-value-no-permission/spin.toml b/tests/runtime-tests/tests/key-value-no-permission/spin.toml deleted file mode 100644 index a9eee3a87..000000000 --- a/tests/runtime-tests/tests/key-value-no-permission/spin.toml +++ /dev/null @@ -1,13 +0,0 @@ -spin_manifest_version = 2 - -[application] -name = "key-value" -authors = ["Fermyon Engineering "] -version = "0.1.0" - -[[trigger.http]] -route = "/" -component = "test" - -[component.test] -source = "%{source=key-value}" diff --git a/tests/runtime-tests/tests/key-value/spin.toml b/tests/runtime-tests/tests/key-value/spin.toml deleted file mode 100644 index ce7c2853c..000000000 --- a/tests/runtime-tests/tests/key-value/spin.toml +++ /dev/null @@ -1,14 +0,0 @@ -spin_manifest_version = 2 - -[application] -name = "key-value" -authors = ["Fermyon Engineering "] -version = "0.1.0" - -[[trigger.http]] -route = "/" -component = "test" - -[component.test] -source = "%{source=key-value}" -key_value_stores = ["default"] diff --git a/tests/runtime-tests/tests/outbound-mqtt-variable-permission/services b/tests/runtime-tests/tests/outbound-mqtt-variable-permission/services deleted file mode 100644 index 1d00b34f3..000000000 --- a/tests/runtime-tests/tests/outbound-mqtt-variable-permission/services +++ /dev/null @@ -1 +0,0 @@ -mqtt \ No newline at end of file diff --git a/tests/runtime-tests/tests/outbound-mqtt-variable-permission/spin.toml b/tests/runtime-tests/tests/outbound-mqtt-variable-permission/spin.toml deleted file mode 100644 index ffff0180d..000000000 --- a/tests/runtime-tests/tests/outbound-mqtt-variable-permission/spin.toml +++ /dev/null @@ -1,19 +0,0 @@ -spin_manifest_version = 2 - -[application] -name = "outbound-mqtt" -authors = ["Suneet Nangia "] -version = "0.1.0" - -[variables] -mqtt_server = { default = "localhost" } - -[[trigger.http]] -route = "/" -component = "test" - -[component.test] -source = "%{source=outbound-mqtt}" -allowed_outbound_hosts = ["mqtt://{{ mqtt_server }}:%{port=1883}"] -# To test anonymous MQTT authentication, remove the values from MQTT_USERNAME and MQTT_PASSWORD env variables. -environment = { MQTT_ADDRESS = "mqtt://localhost:%{port=1883}?client_id=spintest", MQTT_USERNAME = "user", MQTT_PASSWORD = "password", MQTT_KEEP_ALIVE_INTERVAL = "30" } diff --git a/tests/runtime-tests/tests/outbound-mqtt/services b/tests/runtime-tests/tests/outbound-mqtt/services deleted file mode 100644 index 1d00b34f3..000000000 --- a/tests/runtime-tests/tests/outbound-mqtt/services +++ /dev/null @@ -1 +0,0 @@ -mqtt \ No newline at end of file diff --git a/tests/runtime-tests/tests/outbound-mqtt/spin.toml b/tests/runtime-tests/tests/outbound-mqtt/spin.toml deleted file mode 100644 index a1b03ed47..000000000 --- a/tests/runtime-tests/tests/outbound-mqtt/spin.toml +++ /dev/null @@ -1,16 +0,0 @@ -spin_manifest_version = 2 - -[application] -name = "outbound-mqtt" -authors = ["Suneet Nangia "] -version = "0.1.0" - -[[trigger.http]] -route = "/" -component = "test" - -[component.test] -source = "%{source=outbound-mqtt}" -allowed_outbound_hosts = ["mqtt://localhost:%{port=1883}"] -# To test anonymous MQTT authentication, remove the values from MQTT_USERNAME and MQTT_PASSWORD env variables. -environment = { MQTT_ADDRESS = "mqtt://localhost:%{port=1883}?client_id=spintest", MQTT_USERNAME = "user", MQTT_PASSWORD = "password", MQTT_KEEP_ALIVE_INTERVAL = "30" } diff --git a/tests/runtime-tests/tests/outbound-redis-no-permission/error.txt b/tests/runtime-tests/tests/outbound-redis-no-permission/error.txt deleted file mode 100644 index bdb4a74a8..000000000 --- a/tests/runtime-tests/tests/outbound-redis-no-permission/error.txt +++ /dev/null @@ -1 +0,0 @@ -Error::InvalidAddress \ No newline at end of file diff --git a/tests/runtime-tests/tests/outbound-redis-no-permission/spin.toml b/tests/runtime-tests/tests/outbound-redis-no-permission/spin.toml deleted file mode 100644 index 95e75b14b..000000000 --- a/tests/runtime-tests/tests/outbound-redis-no-permission/spin.toml +++ /dev/null @@ -1,14 +0,0 @@ -spin_manifest_version = 2 - -[application] -name = "outbound-redis" -authors = ["Fermyon Engineering "] -version = "0.1.0" - -[[trigger.http]] -route = "/" -component = "test" - -[component.test] -source = "%{source=outbound-redis}" -environment = { REDIS_ADDRESS = "redis://localhost:6379" } diff --git a/tests/runtime-tests/tests/outbound-redis-variable-permission/services b/tests/runtime-tests/tests/outbound-redis-variable-permission/services deleted file mode 100644 index 74b362f93..000000000 --- a/tests/runtime-tests/tests/outbound-redis-variable-permission/services +++ /dev/null @@ -1 +0,0 @@ -redis \ No newline at end of file diff --git a/tests/runtime-tests/tests/outbound-redis-variable-permission/spin.toml b/tests/runtime-tests/tests/outbound-redis-variable-permission/spin.toml deleted file mode 100644 index 176e5b14a..000000000 --- a/tests/runtime-tests/tests/outbound-redis-variable-permission/spin.toml +++ /dev/null @@ -1,18 +0,0 @@ -spin_manifest_version = 2 - -[application] -name = "outbound-redis" -authors = ["Fermyon Engineering "] -version = "0.1.0" - -[variables] -redis_host = { default = "localhost" } - -[[trigger.http]] -route = "/" -component = "test" - -[component.test] -source = "%{source=outbound-redis}" -environment = { REDIS_ADDRESS = "redis://localhost:%{port=6379}" } -allowed_outbound_hosts = ["redis://{{ redis_host }}:%{port=6379}"] diff --git a/tests/runtime-tests/tests/outbound-redis/services b/tests/runtime-tests/tests/outbound-redis/services deleted file mode 100644 index 74b362f93..000000000 --- a/tests/runtime-tests/tests/outbound-redis/services +++ /dev/null @@ -1 +0,0 @@ -redis \ No newline at end of file diff --git a/tests/runtime-tests/tests/outbound-redis/spin.toml b/tests/runtime-tests/tests/outbound-redis/spin.toml deleted file mode 100644 index 15b54a338..000000000 --- a/tests/runtime-tests/tests/outbound-redis/spin.toml +++ /dev/null @@ -1,15 +0,0 @@ -spin_manifest_version = 2 - -[application] -name = "outbound-redis" -authors = ["Fermyon Engineering "] -version = "0.1.0" - -[[trigger.http]] -route = "/" -component = "test" - -[component.test] -source = "%{source=outbound-redis}" -environment = { REDIS_ADDRESS = "redis://localhost:%{port=6379}" } -allowed_outbound_hosts = ["redis://localhost:%{port=6379}"] diff --git a/tests/runtime-tests/tests/sqlite-no-permission/error.txt b/tests/runtime-tests/tests/sqlite-no-permission/error.txt deleted file mode 100644 index 11a87ef97..000000000 --- a/tests/runtime-tests/tests/sqlite-no-permission/error.txt +++ /dev/null @@ -1 +0,0 @@ -Error::AccessDenied \ No newline at end of file diff --git a/tests/runtime-tests/tests/sqlite-no-permission/spin.toml b/tests/runtime-tests/tests/sqlite-no-permission/spin.toml deleted file mode 100644 index b684a86ed..000000000 --- a/tests/runtime-tests/tests/sqlite-no-permission/spin.toml +++ /dev/null @@ -1,13 +0,0 @@ -spin_manifest_version = 2 - -[application] -name = "sqlite" -authors = ["Ryan Levick "] -version = "0.1.0" - -[[trigger.http]] -route = "/" -component = "test" - -[component.test] -source = "%{source=sqlite}" diff --git a/tests/runtime-tests/tests/sqlite/spin.toml b/tests/runtime-tests/tests/sqlite/spin.toml deleted file mode 100644 index 1bc45fc4e..000000000 --- a/tests/runtime-tests/tests/sqlite/spin.toml +++ /dev/null @@ -1,14 +0,0 @@ -spin_manifest_version = 2 - -[application] -name = "sqlite" -authors = ["Ryan Levick "] -version = "0.1.0" - -[[trigger.http]] -route = "/" -component = "test" - -[component.test] -source = "%{source=sqlite}" -sqlite_databases = ["default"] diff --git a/tests/runtime-tests/tests/tcp-sockets-ip-range-variable-permission/services b/tests/runtime-tests/tests/tcp-sockets-ip-range-variable-permission/services deleted file mode 100644 index 272b7c063..000000000 --- a/tests/runtime-tests/tests/tcp-sockets-ip-range-variable-permission/services +++ /dev/null @@ -1 +0,0 @@ -tcp-echo \ No newline at end of file diff --git a/tests/runtime-tests/tests/tcp-sockets-ip-range-variable-permission/spin.toml b/tests/runtime-tests/tests/tcp-sockets-ip-range-variable-permission/spin.toml deleted file mode 100644 index e50614a93..000000000 --- a/tests/runtime-tests/tests/tcp-sockets-ip-range-variable-permission/spin.toml +++ /dev/null @@ -1,19 +0,0 @@ -spin_manifest_version = 2 - -[application] -name = "tcp-sockets" -authors = ["Fermyon Engineering "] -version = "0.1.0" - -[variables] -addr_prefix = { default = "127.0.0.0" } -prefix_len = { default = "24" } - -[[trigger.http]] -route = "/" -component = "test" - -[component.test] -source = "%{source=tcp-sockets}" -environment = { ADDRESS = "127.0.0.1:%{port=7}" } -allowed_outbound_hosts = ["*://{{ addr_prefix }}/{{ prefix_len }}:%{port=7}"] diff --git a/tests/runtime-tests/tests/tcp-sockets-ip-range/services b/tests/runtime-tests/tests/tcp-sockets-ip-range/services deleted file mode 100644 index 272b7c063..000000000 --- a/tests/runtime-tests/tests/tcp-sockets-ip-range/services +++ /dev/null @@ -1 +0,0 @@ -tcp-echo \ No newline at end of file diff --git a/tests/runtime-tests/tests/tcp-sockets-ip-range/spin.toml b/tests/runtime-tests/tests/tcp-sockets-ip-range/spin.toml deleted file mode 100644 index b4f36489f..000000000 --- a/tests/runtime-tests/tests/tcp-sockets-ip-range/spin.toml +++ /dev/null @@ -1,15 +0,0 @@ -spin_manifest_version = 2 - -[application] -name = "tcp-sockets" -authors = ["Fermyon Engineering "] -version = "0.1.0" - -[[trigger.http]] -route = "/" -component = "test" - -[component.test] -source = "%{source=tcp-sockets}" -environment = { ADDRESS = "127.0.0.1:%{port=7}" } -allowed_outbound_hosts = ["*://127.0.0.0/24:%{port=7}"] diff --git a/tests/runtime-tests/tests/tcp-sockets-no-ip-permission/error.txt b/tests/runtime-tests/tests/tcp-sockets-no-ip-permission/error.txt deleted file mode 100644 index d62c3f596..000000000 --- a/tests/runtime-tests/tests/tcp-sockets-no-ip-permission/error.txt +++ /dev/null @@ -1 +0,0 @@ -access-denied \ No newline at end of file diff --git a/tests/runtime-tests/tests/tcp-sockets-no-ip-permission/spin.toml b/tests/runtime-tests/tests/tcp-sockets-no-ip-permission/spin.toml deleted file mode 100644 index f85b16be7..000000000 --- a/tests/runtime-tests/tests/tcp-sockets-no-ip-permission/spin.toml +++ /dev/null @@ -1,16 +0,0 @@ -spin_manifest_version = 2 - -[application] -name = "tcp-sockets" -authors = ["Fermyon Engineering "] -version = "0.1.0" - -[[trigger.http]] -route = "/" -component = "test" - -[component.test] -source = "%{source=tcp-sockets}" -environment = { ADDRESS = "127.0.0.1:6001" } -# Component expects 127.0.0.1 but we only allow 127.0.0.2 -allowed_outbound_hosts = ["*://127.0.0.2:6001"] diff --git a/tests/runtime-tests/tests/tcp-sockets-no-port-permission/error.txt b/tests/runtime-tests/tests/tcp-sockets-no-port-permission/error.txt deleted file mode 100644 index d62c3f596..000000000 --- a/tests/runtime-tests/tests/tcp-sockets-no-port-permission/error.txt +++ /dev/null @@ -1 +0,0 @@ -access-denied \ No newline at end of file diff --git a/tests/runtime-tests/tests/tcp-sockets-no-port-permission/spin.toml b/tests/runtime-tests/tests/tcp-sockets-no-port-permission/spin.toml deleted file mode 100644 index 4c1b72ce2..000000000 --- a/tests/runtime-tests/tests/tcp-sockets-no-port-permission/spin.toml +++ /dev/null @@ -1,16 +0,0 @@ -spin_manifest_version = 2 - -[application] -name = "tcp-sockets" -authors = ["Fermyon Engineering "] -version = "0.1.0" - -[[trigger.http]] -route = "/" -component = "test" - -[component.test] -source = "%{source=tcp-sockets}" -environment = { ADDRESS = "127.0.0.1:6001" } -# Component expects port 6001 but we allow 6002 -allowed_outbound_hosts = ["*://127.0.0.1:6002"] diff --git a/tests/runtime-tests/tests/tcp-sockets/services b/tests/runtime-tests/tests/tcp-sockets/services deleted file mode 100644 index 272b7c063..000000000 --- a/tests/runtime-tests/tests/tcp-sockets/services +++ /dev/null @@ -1 +0,0 @@ -tcp-echo \ No newline at end of file diff --git a/tests/runtime-tests/tests/tcp-sockets/spin.toml b/tests/runtime-tests/tests/tcp-sockets/spin.toml deleted file mode 100644 index 6bb3526f9..000000000 --- a/tests/runtime-tests/tests/tcp-sockets/spin.toml +++ /dev/null @@ -1,15 +0,0 @@ -spin_manifest_version = 2 - -[application] -name = "tcp-sockets" -authors = ["Fermyon Engineering "] -version = "0.1.0" - -[[trigger.http]] -route = "/" -component = "test" - -[component.test] -source = "%{source=tcp-sockets}" -environment = { ADDRESS = "127.0.0.1:%{port=7}" } -allowed_outbound_hosts = ["*://127.0.0.1:%{port=7}"] diff --git a/tests/runtime-tests/tests/variables/spin.toml b/tests/runtime-tests/tests/variables/spin.toml deleted file mode 100644 index a961fbd89..000000000 --- a/tests/runtime-tests/tests/variables/spin.toml +++ /dev/null @@ -1,16 +0,0 @@ -spin_manifest_version = 2 - -[application] -name = "variables" -authors = ["Fermyon Engineering "] -version = "0.1.0" - -[[trigger.http]] -route = "/" -component = "test" - -[component.test] -source = "%{source=variables}" - -[component.test.variables] -variable = "value" diff --git a/tests/runtime.rs b/tests/runtime.rs index d541313e7..e2cb1ee79 100644 --- a/tests/runtime.rs +++ b/tests/runtime.rs @@ -25,4 +25,14 @@ mod runtime_tests { .expect("failed to bootstrap runtime tests tests") .run(); } + + #[test] + fn conformance_tests() { + conformance_tests::run_tests(move |test| conformance::run_test(test, &spin_binary())) + .unwrap(); + } + + fn spin_binary() -> PathBuf { + env!("CARGO_BIN_EXE_spin").into() + } }