wrpc 0.16.0

WebAssembly component-native RPC framework based on WIT
Documentation
use anyhow::{ensure, Context};
use tokio::process::Command;
use tracing::instrument;

mod common;

#[cfg(feature = "nats")]
#[instrument(ret)]
#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn go_bindgen() -> anyhow::Result<()> {
    use core::time::Duration;

    use anyhow::{anyhow, bail};
    use common::assert_async;
    use tokio::fs;
    use tokio::time::sleep;
    use tracing::info;

    if let Err(err) = fs::remove_dir_all("tests/go/bindings").await {
        match err.kind() {
            std::io::ErrorKind::NotFound => {}
            _ => bail!(anyhow!(err).context("failed to remove `test/go/bindings`")),
        }
    }
    let status = Command::new("go")
        .current_dir("tests/go")
        .args(["generate", "./..."])
        .env("WIT_BINDGEN_WRPC", env!("CARGO_BIN_EXE_wit-bindgen-wrpc"))
        .kill_on_drop(true)
        .status()
        .await
        .context("failed to call `go generate`")?;
    ensure!(status.success(), "`go generate` failed");

    let status = Command::new("go")
        .current_dir("tests/go")
        .args(["test", "-v", "-failfast"])
        .kill_on_drop(true)
        .status()
        .await
        .context("failed to call `go test`")?;
    ensure!(status.success(), "`go test` failed");

    wrpc_test::with_nats(|port, nats_client| async move {
        mod bindings {
            wit_bindgen_wrpc::generate!({
                world: "sync-client",
                path: "tests/wit",
                additional_derives: [::core::cmp::PartialEq],
            });
        }

        use bindings::foo;
        use bindings::wrpc_test::integration::sync;
        use bindings::wrpc_test::integration::sync::{Abc, Foobar, Rec, RecNested, Var};

        info!("starting `sync-server-nats`");
        let mut server = Command::new("go")
            .current_dir("tests/go")
            .args([
                "run",
                "./cmd/sync-server-nats",
                &format!("nats://localhost:{port}"),
            ])
            .kill_on_drop(true)
            .spawn()
            .context("failed to run `sync-server-nats`")?;

        let client = wrpc_transport_nats::Client::new(nats_client, "go", None)
            .await
            .context("failed to construct client")?;

        // TODO: Remove the need for this
        sleep(Duration::from_secs(1)).await;

        info!("calling `wrpc-test:integration/sync-client.foo.f`");
        let v = foo::f(&client, None, "f")
            .await
            .context("failed to call `wrpc-test:integration/sync-client.foo.f`")?;
        ensure!(v == 42);

        info!("calling `wrpc-test:integration/sync-client.foo.foo`");
        foo::foo(&client, None, "foo")
            .await
            .context("failed to call `wrpc-test:integration/sync-client.foo.foo`")?;

        info!("calling `wrpc-test:integration/sync.fallible`");
        let res = sync::fallible(&client, None, true)
            .await
            .context("failed to call `wrpc-test:integration/sync.fallible`")?;
        ensure!(res == Ok(true));

        info!("calling `wrpc-test:integration/sync.fallible`");
        let res = sync::fallible(&client, None, false)
            .await
            .context("failed to call `wrpc-test:integration/sync.fallible`")?;
        ensure!(res == Err("test".to_string()));

        info!("calling `wrpc-test:integration/sync.numbers`");
        let (a, b, c, d, e, f, g, h, i, j) = sync::numbers(&client, None)
            .await
            .context("failed to call `wrpc-test:integration/sync.numbers`")?;
        ensure!(a == 1);
        ensure!(b == 2);
        ensure!(c == 3);
        ensure!(d == 4);
        ensure!(e == 5);
        ensure!(f == 6);
        ensure!(g == 7);
        ensure!(h == 8);
        ensure!(i == 9.);
        ensure!(j == 10.);

        info!("calling `wrpc-test:integration/sync.with-flags`");
        let v = sync::with_flags(&client, None, true, false, true)
            .await
            .context("failed to call `wrpc-test:integration/sync.with-flags`")?;
        ensure!(v == Abc::A | Abc::C, "{v:?}");

        info!("calling `wrpc-test:integration/sync.with-variant-option`");
        let v = sync::with_variant_option(&client, None, false)
            .await
            .context("failed to call `wrpc-test:integration/sync.with-variant-option`")?;
        ensure!(v.is_none(), "{v:?}");

        info!("calling `wrpc-test:integration/sync.with-variant-option`");
        let v = sync::with_variant_option(&client, None, true)
            .await
            .context("failed to call `wrpc-test:integration/sync.with-variant-option`")?;
        ensure!(
            v == Some(Var::Var(Rec {
                nested: RecNested {
                    foo: "bar".to_string()
                }
            })),
            "{v:?}"
        );

        info!("calling `wrpc-test:integration/sync.with-variant-list`");
        let v = sync::with_variant_list(&client, None)
            .await
            .context("failed to call `wrpc-test:integration/sync.with-variant-list`")?;
        ensure!(
            v == [
                Var::Empty,
                Var::Var(Rec {
                    nested: RecNested {
                        foo: "foo".to_string()
                    }
                }),
                Var::Empty,
                Var::Empty,
                Var::Empty,
                Var::Var(Rec {
                    nested: RecNested {
                        foo: "bar".to_string()
                    }
                }),
            ],
            "{v:?}"
        );

        info!("calling `wrpc-test:integration/sync.with-record`");
        let v = sync::with_record(&client, None)
            .await
            .context("failed to call `wrpc-test:integration/sync.with-record`")?;
        ensure!(
            v == Rec {
                nested: RecNested {
                    foo: "foo".to_string()
                }
            },
            "{v:?}"
        );

        info!("calling `wrpc-test:integration/sync.with-record-list`");
        let v = sync::with_record_list(&client, None, 0)
            .await
            .context("failed to call `wrpc-test:integration/sync.with-record-list`")?;
        ensure!(v.is_empty(), "{v:?}");

        info!("calling `wrpc-test:integration/sync.with-record-list`");
        let v = sync::with_record_list(&client, None, 3)
            .await
            .context("failed to call `wrpc-test:integration/sync.with-record-list`")?;
        ensure!(
            v == [
                Rec {
                    nested: RecNested {
                        foo: "0".to_string()
                    }
                },
                Rec {
                    nested: RecNested {
                        foo: "1".to_string()
                    }
                },
                Rec {
                    nested: RecNested {
                        foo: "2".to_string()
                    }
                },
            ],
            "{v:?}",
        );

        info!("calling `wrpc-test:integration/sync.with-record-tuple`");
        let v = sync::with_record_tuple(&client, None)
            .await
            .context("failed to call `wrpc-test:integration/sync.with-record-tuple`")?;
        ensure!(
            v == (
                Rec {
                    nested: RecNested {
                        foo: "0".to_string()
                    }
                },
                Rec {
                    nested: RecNested {
                        foo: "1".to_string()
                    }
                },
            ),
            "{v:?}",
        );

        info!("calling `wrpc-test:integration/sync.with-enum`");
        let v = sync::with_enum(&client, None)
            .await
            .context("failed to call `wrpc-test:integration/sync.with-enum-tuple`")?;
        ensure!(v == Foobar::Bar, "{v:?}",);

        server
            .start_kill()
            .context("failed to kill `sync-server-nats`")?;
        server
            .wait_with_output()
            .await
            .context("failed to wait for `sync-server-nats` to exit")?;

        Ok(())
    })
    .await?;

    wrpc_test::with_nats(|port, nats_client| async move {
        info!("starting `async-server-nats`");
        let mut server = Command::new("go")
            .current_dir("tests/go")
            .args([
                "run",
                "./cmd/async-server-nats",
                &format!("nats://localhost:{port}"),
            ])
            .kill_on_drop(true)
            .spawn()
            .context("failed to run `async-server-nats`")?;

        // TODO: Remove the need for this
        sleep(Duration::from_secs(1)).await;

        let client = wrpc_transport_nats::Client::new(nats_client, "go", None)
            .await
            .context("failed to construct client")?;
        assert_async(&client).await?;

        server
            .start_kill()
            .context("failed to kill `async-server-nats`")?;
        server
            .wait_with_output()
            .await
            .context("failed to wait for `async-server-nats` to exit")?;

        Ok(())
    })
    .await?;

    Ok(())
}

#[instrument(ret)]
#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn go() -> anyhow::Result<()> {
    let status = Command::new("go")
        .current_dir("go")
        .args(["test", "-v", "-failfast", "./..."])
        .kill_on_drop(true)
        .status()
        .await
        .context("failed to call `go test`")?;
    ensure!(status.success(), "`go test` failed");
    Ok(())
}