diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8f840c2f..1386b69b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,11 +26,11 @@ jobs: run: cargo fmt --check - name: Clippy run: cargo clippy -- -D warnings - test: + build: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest] + os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 @@ -42,11 +42,13 @@ jobs: ~/.cargo/git target key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + - name: Install Wasm32-wasi target + run: rustup target add wasm32-wasi + - name: Build Rust examples + working-directory: ./examples + run: make all + - name: Build wws on release mode + if: steps.check_file_release.outputs.files_exists != 'true' + run: cargo build --verbose --release - name: Test - run: cargo test --workspace --exclude wasm-workers-quick-js-engine - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Build - run: cargo build --verbose \ No newline at end of file + run: cargo test --workspace --exclude wasm-workers-quick-js-engine -- --show-output diff --git a/Cargo.lock b/Cargo.lock index cf198478..4ded4779 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1139,6 +1139,12 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + [[package]] name = "futures-sink" version = "0.3.26" @@ -1158,9 +1164,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" dependencies = [ "futures-core", + "futures-io", "futures-task", + "memchr", "pin-project-lite", "pin-utils", + "slab", ] [[package]] @@ -2585,6 +2594,7 @@ dependencies = [ "libc", "memchr", "mio", + "num_cpus", "parking_lot", "pin-project-lite", "signal-hook-registry", @@ -2956,6 +2966,7 @@ dependencies = [ "env_logger 0.9.3", "openssl", "prettytable-rs", + "reqwest", "wws-config", "wws-router", "wws-runtimes-manager", diff --git a/Cargo.toml b/Cargo.toml index a2d0c13a..3418510f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,9 @@ wws-router = { workspace = true } wws-server = { workspace = true } wws-runtimes-manager = { workspace = true } +[dev-dependencies] +reqwest = { version = "0.11", features = ["blocking"] } + [target.x86_64-unknown-linux-musl.dependencies] openssl = { version = "=0.10.48", features = ["vendored"] } diff --git a/examples/Makefile b/examples/Makefile index 9d239cda..6ae334d2 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -20,4 +20,4 @@ pdf-create: cargo build --target wasm32-wasi --release && \ mv target/wasm32-wasi/release/pdf-create.wasm ./index.wasm -all: rust-basic rust-kv rust-params pdf-create +all: rust-basic rust-kv rust-params diff --git a/examples/ruby-basic/.wws.toml b/examples/ruby-basic/.wws.toml new file mode 100644 index 00000000..773b70a6 --- /dev/null +++ b/examples/ruby-basic/.wws.toml @@ -0,0 +1,44 @@ +version = 1 + +[[repositories]] +name = "wasmlabs" +url = "https://workers.wasmlabs.dev/repository/v1/index.toml" + +[[repositories.runtimes]] +name = "ruby" +version = "3.2.0+20230215-1" +tags = [ + "latest", + "3.2", + "3.2.0", +] +status = "active" +extensions = ["rb"] +args = [ + "--", + "/src/index.rb", +] + +[repositories.runtimes.binary] +url = "https://github.com/vmware-labs/webassembly-language-runtimes/releases/download/ruby%2F3.2.0%2B20230215-1349da9/ruby-3.2.0.wasm" +filename = "ruby.wasm" + +[repositories.runtimes.binary.checksum] +type = "sha256" +value = "abe348fba157a756f86194be445c77c99e8ed64ca76495ea07ed984f09eb66ae" + +[repositories.runtimes.polyfill] +url = "https://workers.wasmlabs.dev/repository/v1/files/ruby/3-1/poly.rb" +filename = "poly.rb" + +[repositories.runtimes.polyfill.checksum] +type = "sha256" +value = "449855a5d315879ab0ad830aa6a3f689e68fed4490617ea03efc77c9da64f630" + +[repositories.runtimes.wrapper] +url = "https://workers.wasmlabs.dev/repository/v1/files/ruby/3-1/wrapper.txt" +filename = "wrapper.txt" + +[repositories.runtimes.wrapper.checksum] +type = "sha256" +value = "6d808b4747cf30f82665a38a47e1176513bbdd6ad558c09db03d719e33ad2da0" diff --git a/tests/e2e.rs b/tests/e2e.rs new file mode 100644 index 00000000..51b2d890 --- /dev/null +++ b/tests/e2e.rs @@ -0,0 +1,163 @@ +#[cfg(test)] +mod test { + use std::path::PathBuf; + use std::process::{Child, Command, Stdio}; + use std::{env, io, thread, time}; + + #[cfg(not(target_os = "windows"))] + fn get_wws_path() -> PathBuf { + let path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); + + // Use release when it's available + let wws_path = if path.join("target/release/wws").exists() { + path.join("target/release/wws") + } else { + path.join("target/debug/wws") + }; + + println!("[E2E] Running wws from {}", wws_path.display()); + + wws_path + } + + #[cfg(target_os = "windows")] + fn get_wws_path() -> PathBuf { + let path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); + + // Use release when it's available + let wws_path = if path.join("target/release/wws.exe").exists() { + path.join("target/release/wws.exe") + } else { + path.join("target/debug/wws.exe") + }; + + println!("[E2E] Running wws from {}", wws_path.display()); + + wws_path + } + + fn run(example_path: &str) -> io::Result { + let path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); + let example_path = path.join("examples").join(example_path); + let wws_path = get_wws_path(); + + // Install missing runtimes + println!("[E2E] Installing missing runtimes"); + Command::new(&wws_path) + .current_dir(&example_path) + .args(["runtimes", "install"]) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .status()?; + + // Run the example + println!("[E2E] Running the service"); + Command::new(&wws_path).arg(&example_path).spawn() + } + + fn sleep_for(seconds: u64) { + thread::sleep(time::Duration::from_secs(seconds)); + } + + fn request_body(url: &str) -> Result { + reqwest::blocking::get(url)?.text() + } + + // Check the examples/js-json works + fn run_end_to_end_test(example: &str, waiting_seconds: u64, url: &str, expected_text: &str) { + println!("[E2E] Running example: {example}"); + + let mut child = run(example).expect("Failed to execute command"); + + sleep_for(waiting_seconds); + + let body = match request_body(url) { + Ok(body) => body, + Err(err) => { + eprintln!("[E2E] Error getting the body from the request to {url}"); + eprintln!("[E2E] Error: {}", err); + String::new() + } + }; + + println!("[E2E] Body content: {body}"); + + println!("[E2E] Stopping wws process [{}]", &child.id()); + child.kill().expect("Error stopping wws"); + + // Test + assert!(body.contains(expected_text)); + } + + #[test] + // Use this approach to run tests sequentially + fn test_end_to_end() { + // Allow configuring waiting times. It avoids having long waiting times + // in development, while making it configurable in the CI + let global_timeout = + env::var("E2E_WAITING_TIME").map_or(None, |str| str.parse::().ok()); + + // env (Result(String)) -> map () + + let tests = [ + ( + "rust-basic", + global_timeout.unwrap_or(5), + "http://localhost:8080/basic", + "This page was generated by a Wasm module built from Rust", + ), + ( + "rust-kv", + global_timeout.unwrap_or(5), + "http://localhost:8080/kv", + "Counter: 0", + ), + ( + "rust-params", + global_timeout.unwrap_or(5), + "http://localhost:8080/thisisatest", + "thisisatest", + ), + ( + "js-basic", + global_timeout.unwrap_or(5), + "http://localhost:8080", + "This page was generated by a JavaScript file", + ), + ( + "js-json", + global_timeout.unwrap_or(5), + "http://localhost:8080/handler", + "This message comes from an environment variable", + ), + ( + "js-params", + global_timeout.unwrap_or(10), + "http://localhost:8080/thisisatest", + "thisisatest", + ), + ( + "python-basic", + global_timeout.unwrap_or(20), + "http://localhost:8080/", + "This page was generated by a Python script", + ), + ( + "python-mount", + global_timeout.unwrap_or(20), + "http://localhost:8080/", + "This page was loaded from a mounted file", + ), + ( + "ruby-basic", + global_timeout.unwrap_or(20), + "http://localhost:8080/", + "This page was generated by a Ruby script", + ), + ]; + + for (example, waiting_seconds, url, expected_text) in tests { + run_end_to_end_test(example, waiting_seconds, url, expected_text); + } + } +}