cargo-component 0.21.1

A cargo extension for authoring WebAssembly components
Documentation
use std::fs;

use anyhow::Result;
use assert_cmd::prelude::*;
use predicates::{str::contains, Predicate};
use tempfile::TempDir;

use crate::support::*;

mod support;

#[test]
fn help() {
    for arg in ["help new", "new -h", "new --help"] {
        cargo_component(arg.split_whitespace())
            .assert()
            .stdout(contains(
                "Create a new WebAssembly component package at <path>",
            ))
            .success();
    }
}

#[test]
fn it_creates_the_expected_files_for_bin() -> Result<()> {
    let dir = TempDir::new()?;

    cargo_component(["new", "--bin", "foo"])
        .current_dir(dir.path())
        .assert()
        .stderr(contains("Updated manifest of package `foo"))
        .success();

    let proj_dir = dir.path().join("foo");

    assert!(proj_dir.join("Cargo.toml").is_file());
    assert!(!proj_dir.join("wit/world.wit").is_file());
    assert!(!proj_dir.join("src").join("lib.rs").is_file());
    assert!(proj_dir.join("src").join("main.rs").is_file());
    assert!(proj_dir.join(".vscode").join("settings.json").is_file());

    Ok(())
}

#[test]
fn it_creates_the_expected_files() -> Result<()> {
    let dir = TempDir::new()?;

    cargo_component(["new", "--lib", "foo"])
        .current_dir(dir.path())
        .assert()
        .stderr(contains("Updated manifest of package `foo`"))
        .success();

    let proj_dir = dir.path().join("foo");

    assert!(proj_dir.join("Cargo.toml").is_file());
    assert!(proj_dir.join("wit/world.wit").is_file());
    assert!(proj_dir.join("src").join("lib.rs").is_file());
    assert!(!proj_dir.join("src").join("main.rs").is_file());
    assert!(proj_dir.join(".vscode").join("settings.json").is_file());

    Ok(())
}

#[test]
fn it_supports_editor_option() -> Result<()> {
    let dir = TempDir::new()?;

    cargo_component(["new", "--lib", "foo", "--editor", "none"])
        .current_dir(dir.path())
        .assert()
        .stderr(contains("Updated manifest of package `foo"))
        .success();

    let proj_dir = dir.path().join("foo");

    assert!(proj_dir.join("Cargo.toml").is_file());
    assert!(proj_dir.join("wit/world.wit").is_file());
    assert!(proj_dir.join("src").join("lib.rs").is_file());
    assert!(!proj_dir.join(".vscode").is_dir());

    Ok(())
}

#[test]
fn it_supports_edition_option() -> Result<()> {
    let dir = TempDir::new()?;

    cargo_component(["new", "--lib", "foo", "--edition", "2018"])
        .current_dir(dir.path())
        .assert()
        .stderr(contains("Updated manifest of package `foo"))
        .success();

    let proj_dir = dir.path().join("foo");

    assert!(fs::read_to_string(proj_dir.join("Cargo.toml"))?.contains("edition = \"2018\""));

    Ok(())
}

#[test]
fn it_supports_name_option() -> Result<()> {
    let dir = TempDir::new()?;

    cargo_component(["new", "--lib", "foo", "--name", "bar"])
        .current_dir(dir.path())
        .assert()
        .stderr(contains("Updated manifest of package `bar`"))
        .success();

    let proj_dir = dir.path().join("foo");

    assert!(fs::read_to_string(proj_dir.join("Cargo.toml"))?.contains("name = \"bar\""));

    Ok(())
}

#[test]
fn it_rejects_rust_keywords() -> Result<()> {
    let dir = TempDir::new()?;

    cargo_component(["new", "--lib", "foo", "--name", "fn"])
        .current_dir(dir.path())
        .assert()
        .stderr(contains(
            "the name `fn` cannot be used as a package name, it is a Rust keyword",
        ))
        .failure();

    Ok(())
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn it_targets_a_world() -> Result<()> {
    let (server, config, _) = spawn_server(Vec::<String>::new()).await?;

    publish_wit(
        config,
        "test:bar",
        "1.2.3",
        r#"package test:bar@1.2.3;

interface bar {
    resource a {
        constructor(a: borrow<a>);
        a: static func(a: borrow<a>) -> a;
        b: func(a: a);
    }

    resource b {
        constructor(a: borrow<a>);
        a: static func(a: borrow<a>) -> a;
        b: func(a: a);
    }

    w: func(a: a) -> a;
    x: func(b: b) -> b;
    y: func(a: borrow<a>);
    z: func(b: borrow<b>);
}

interface not-exported {
    resource some {
        hello: func() -> string;
    }
}

interface is-exported {
    resource else {
        hello: func() -> string;
    }
}

interface is-exported-and-aliased {
    resource something {
        hello: func() -> string;
    }
}

interface baz {
    use bar.{a as a2};
    use not-exported.{some};
    use is-exported.{else};
    use is-exported-and-aliased.{something as someelse};

    resource a {
        constructor(a: borrow<a>);
        a: static func(a: borrow<a>) -> a;
        b: func(a: a);
        c: static func(some: borrow<some>) -> a;
        d: static func(else: borrow<else>) -> a;
        e: static func(else: borrow<someelse>) -> a;
    }

    resource b {
        constructor(a: borrow<a>);
        a: static func(a: borrow<a>) -> a;
        b: func(a: a);
    }

    u: func(some: borrow<some>) -> some;
    v: func(a2: borrow<a2>) -> a2;
    w: func(a: a) -> a;
    x: func(b: b) -> b;
    y: func(a: borrow<a>);
    z: func(b: borrow<b>);
}

interface another {
    resource yet-another {
        hello: func() -> string;
    }
    resource empty {}
}

world not-used {
    export not-exported;
}

world foo {
    use another.{yet-another, empty};

    resource file {
        open: static func(path: string) -> file;
        path: func() -> string;
    }
    import foo: func() -> file;
    export bar: func(file: borrow<file>) -> file;
    export bar;
    export baz;
    export is-exported;
    export is-exported-and-aliased;

    export another;
    export another-func: func(yet: borrow<yet-another>) -> result;
    export another-func-empty: func(empty: borrow<empty>) -> result;
}"#,
    )
    .await?;

    let project = server.project("component", true, ["--target", "test:bar/foo@1.0.0"])?;
    project
        .cargo_component(["build"])
        .assert()
        .stderr(contains(
            "Finished `dev` profile [unoptimized + debuginfo] target(s)",
        ))
        .success();
    validate_component(&project.debug_wasm("component"))?;

    Ok(())
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn it_errors_if_target_does_not_exist() -> Result<()> {
    let (server, _, _) = spawn_server(["foo"]).await?;

    match server.project("component", true, ["--target", "foo:bar@1.0.0"]) {
        Ok(_) => panic!("expected error"),
        Err(e) => assert!(
            contains("package `foo:bar` was not found").eval(&e.to_string()),
            "Should contain error message {e:?}"
        ),
    }

    Ok(())
}

#[test]
fn it_supports_the_command_option() -> Result<()> {
    let dir = TempDir::new()?;

    cargo_component(["new", "--command", "foo"])
        .current_dir(dir.path())
        .assert()
        .try_success()?;

    Ok(())
}

#[test]
fn it_supports_the_reactor_option() -> Result<()> {
    let dir = TempDir::new()?;

    cargo_component(["new", "--reactor", "foo"])
        .current_dir(dir.path())
        .assert()
        .try_success()?;

    Ok(())
}

#[test]
fn it_supports_the_proxy_option() -> Result<()> {
    let dir: TempDir = TempDir::new()?;

    cargo_component(["new", "--lib", "--proxy", "foo"])
        .current_dir(dir.path())
        .assert()
        .try_success()?;

    assert!(fs::read_to_string(dir.path().join("foo/Cargo.toml"))?.contains("proxy = true"));

    Ok(())
}