Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions examples/wasip1/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
build:
@componentize-go build --wasip1

.PHONY: run
run: build
@wasmtime run main.wasm
14 changes: 14 additions & 0 deletions examples/wasip1/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# `wasip1` Example

## Usage

### Prerequisites

- [**componentize-go**](https://github.com/bytecodealliance/componentize-go) - Latest version
- [**go**](https://go.dev/dl/) - v1.25+
- [**wasmtime**](https://github.com/bytecodealliance/wasmtime) - v40.0.2

### Run

```sh
make run
7 changes: 7 additions & 0 deletions examples/wasip1/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

import "fmt"

func main() {
fmt.Println("Hello, World!")
}
2 changes: 2 additions & 0 deletions examples/wasip2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- [**wasmtime**](https://github.com/bytecodealliance/wasmtime) - v40.0.2

### Run

```sh
# Start the application
make run
Expand All @@ -18,6 +19,7 @@ curl localhost:8080
```

### Run unit tests

```sh
make run-tests
```
4 changes: 4 additions & 0 deletions examples/wasip3/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# `wasip3` Example

This demonstrates how to implement a `wasi:http@0.3.0-rc-2025-09-16` handler
using Go, based on a new `wit-bindgen-go` bindings generator which supports
idiomatic, goroutine-based concurrency on top of the [Component Model
Expand All @@ -11,7 +12,9 @@ Go](https://github.com/dicej/go/releases/tag/go1.25.5-wasi-on-idle). Once
everything is merged, we'll be able to switch to the upstream releases.

## Building and Running

### Prerequisites

- [**componentize-go**](https://github.com/bytecodealliance/componentize-go) - Latest version
- [**go**](https://github.com/dicej/go/releases/tag/go1.25.5-wasi-on-idle) - The [Makefile](./Makefile) installs the patched version of Go.
- [**wasmtime**](https://github.com/bytecodealliance/wasmtime) - v40.0.2
Expand Down Expand Up @@ -56,4 +59,5 @@ curl -i \
```

## Note

This was originally built by [@dicej](https://github.com/dicej) and has been adapted from the [original example](https://github.com/dicej/go-wasi-http-example/tree/main) to use componentize-go.
8 changes: 2 additions & 6 deletions src/cmd_build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,12 @@ pub fn build_module(
None => ".",
};

// TODO: for when/if we decide to allow users to build wasm modules without componentizing them
#[allow(unused_variables)]
// The -buildmode flag mutes the module's output, so it is ommitted
let module_args = [
"build",
"-C",
module_path,
"-buildmode=c-shared",
"-ldflags=-checklinkname=0",
"-o",
out_path,
];
Expand All @@ -66,9 +65,6 @@ pub fn build_module(
];

let output = if only_wasip1 {
unimplemented!("Building wasip1 Go apps isn't supported quite yet.");
// TODO: for when/if we decide to allow users to build wasm modules without componentizing them
#[allow(unreachable_code)]
Command::new(&go)
.args(module_args)
.env("GOOS", "wasip1")
Expand Down
4 changes: 3 additions & 1 deletion src/cmd_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ pub fn build_test_module(
.env("GOARCH", "wasm")
.output()?
} else {
unimplemented!("Please use the --wasip1 flag when building unit tests");
unimplemented!(
"Building Go test components is not yet supported. Please use the --wasip1 flag when building unit tests."
);

// TODO: for when we figure out how wasip2 tests are to be run
#[allow(unreachable_code)]
Expand Down
167 changes: 119 additions & 48 deletions tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,76 +92,88 @@ mod tests {
go_bin
}

struct App<'a> {
struct App {
/// The path to the example application
path: String,
/// The WIT world to target
world: String,
world: Option<String>,
/// The output path of the wasm file
wasm_path: String,
/// The path to the directory containing the WIT files
wit_path: String,
wit_path: Option<String>,
/// The child process ID of a running wasm app
process: Option<Child>,
/// Any tests that need to be compiled and run as such
tests: Option<&'a [Test<'a>]>,
tests: Option<Vec<Test>>,
}

struct Test<'a> {
#[derive(Clone)]
struct Test {
should_fail: bool,
pkg_path: &'a str,
pkg_path: String,
}

impl<'a> App<'a> {
impl App {
/// Create a new app runner.
fn new(path: &'a str, world: &'a str, tests: Option<&'a [Test<'a>]>) -> Self {
fn new(
path: &str,
world: Option<&str>,
tests: Option<Vec<Test>>,
is_component: bool,
) -> Self {
let path = componentize_go::utils::make_path_absolute(&PathBuf::from(path))
.expect("failed to make app path absolute");

let world = if is_component {
Some(world.expect("WIT world must be specified").to_string())
} else {
None
};

let wit_path = if is_component {
Some(
path.join("wit")
.to_str()
.expect("wit_path is not valid unicode")
.to_string(),
)
} else {
None
};

App {
path: path
.clone()
.to_str()
.expect("app path is not valid unicode")
.to_string(),
world: world.to_string(),
world,
wasm_path: path
.join("main.wasm")
.to_str()
.expect("wasm_path is not valid unicode")
.to_string(),
wit_path: path
.join("wit")
.to_str()
.expect("wit_path is not valid unicode")
.to_string(),
wit_path,
process: None,
tests,
}
}

// Build unit tests with componentize-go
fn build_tests(&self, go: Option<&PathBuf>) -> Result<()> {
let test_pkgs = if let Some(pkgs) = self.tests {
pkgs
} else {
return Err(anyhow!(
"Please include the test_pkg_paths when creating App::new()"
));
};
fn build_test_modules(&self, go: Option<&PathBuf>) -> Result<()> {
let test_pkgs = self.tests.as_ref().expect("missing test_pkg_paths");

self.generate_bindings(go)?;

let mut test_cmd = Command::new(COMPONENTIZE_GO_PATH.as_path());
test_cmd
.args(["-w", &self.world])
.args(["-d", &self.wit_path])
.args(["-w", self.world.as_ref().expect("missing WIT world")])
.args(["-d", self.wit_path.as_ref().expect("missing WIT path")])
.arg("test")
.arg("--wasip1");

// Add all the paths to the packages that have unit tests to compile
for test in test_pkgs.iter() {
test_cmd.args(["--pkg", test.pkg_path]);
test_cmd.args(["--pkg", &test.pkg_path]);
}

// `go test -c` needs to be in the same path as the go.mod file.
Expand All @@ -183,13 +195,30 @@ mod tests {
Ok(())
}

fn run_tests(&self) -> Result<()> {
fn run_module(&self) -> Result<()> {
let output = Command::new("wasmtime")
.arg("run")
.arg(&self.wasm_path)
.output()?;

if !output.status.success() {
return Err(anyhow!(
"Failed to run wasm module for application at '{}':\n{} ",
&self.wasm_path,
String::from_utf8_lossy(&output.stdout)
));
}

Ok(())
}

fn run_test_modules(&self) -> Result<()> {
let example_dir = PathBuf::from(&self.path);
if let Some(tests) = self.tests {
if let Some(tests) = &self.tests {
let mut test_errors: Vec<String> = vec![];
for test in tests.iter() {
let wasm_file = example_dir.join(componentize_go::cmd_test::get_test_filename(
&PathBuf::from(test.pkg_path),
&PathBuf::from(&test.pkg_path),
));
match Command::new("wasmtime")
.args(["run", wasm_file.to_str().unwrap()])
Expand Down Expand Up @@ -233,15 +262,45 @@ mod tests {
Ok(())
}

/// Build the app with componentize-go.
fn build(&self, go: Option<&PathBuf>) -> Result<()> {
fn build_module(&self, go: Option<&PathBuf>) -> Result<()> {
// Build component
let mut build_cmd = Command::new(COMPONENTIZE_GO_PATH.as_path());
build_cmd
.arg("build")
.arg("--wasip1")
.args(["-o", &self.wasm_path]);

if let Some(go_path) = go.as_ref() {
build_cmd.args(["--go", go_path.to_str().unwrap()]);
}

// Run `go build` in the same directory as the go.mod file.
build_cmd.current_dir(&self.path);

let build_output = build_cmd.output().expect(&format!(
"failed to execute componentize-go for \"{}\"",
self.path
));

if !build_output.status.success() {
return Err(anyhow!(
"failed to build application \"{}\": {}",
self.path,
String::from_utf8_lossy(&build_output.stderr)
));
}

Ok(())
}

fn build_component(&self, go: Option<&PathBuf>) -> Result<()> {
self.generate_bindings(go)?;

// Build component
let mut build_cmd = Command::new(COMPONENTIZE_GO_PATH.as_path());
build_cmd
.args(["-w", &self.world])
.args(["-d", &self.wit_path])
.args(["-w", self.world.as_ref().expect("missing WIT world")])
.args(["-d", self.wit_path.as_ref().expect("missing WIT path")])
.arg("build")
.args(["-o", &self.wasm_path]);

Expand Down Expand Up @@ -270,8 +329,8 @@ mod tests {

fn generate_bindings(&self, go: Option<&PathBuf>) -> Result<()> {
let bindings_output = Command::new(COMPONENTIZE_GO_PATH.as_path())
.args(["-w", &self.world])
.args(["-d", &self.wit_path])
.args(["-w", self.world.as_ref().expect("missing WIT world")])
.args(["-d", self.wit_path.as_ref().expect("missing WIT path")])
.arg("bindings")
.args(["-o", &self.path])
.current_dir(&self.path)
Expand Down Expand Up @@ -307,7 +366,7 @@ mod tests {
}

/// Run the app and check the output.
async fn run(&mut self, route: &str, expected_response: &str) -> Result<()> {
async fn run_component(&mut self, route: &str, expected_response: &str) -> Result<()> {
let listener = TcpListener::bind("127.0.0.1:0").expect("Failed to bind to a free port");
let addr = listener.local_addr().expect("Failed to get local address");
let port = addr.port();
Expand Down Expand Up @@ -345,48 +404,60 @@ mod tests {
}
}

impl<'a> Drop for App<'a> {
impl Drop for App {
fn drop(&mut self) {
if let Some(child) = &mut self.process {
_ = child.kill()
}
}
}

#[test]
fn example_wasip1() {
let app = App::new("../examples/wasip1", None, None, false);
app.build_module(None).expect("failed to build app module");
app.run_module().expect("failed to run app module");
}

#[tokio::test]
async fn example_wasip2() {
let unit_tests = [
let unit_tests = vec![
Test {
should_fail: false,
pkg_path: "./unit_tests_should_pass",
pkg_path: String::from("./unit_tests_should_pass"),
},
Test {
should_fail: true,
pkg_path: "./unit_tests_should_fail",
pkg_path: String::from("./unit_tests_should_fail"),
},
];

let mut app = App::new("../examples/wasip2", "wasip2-example", Some(&unit_tests));
let mut app = App::new(
"../examples/wasip2",
Some("wasip2-example"),
Some(unit_tests),
true,
);

app.build(None).expect("failed to build app");
app.build_component(None).expect("failed to build app");

app.run("/", "Hello, world!")
app.run_component("/", "Hello, world!")
.await
.expect("app failed to run");

app.build_tests(None)
app.build_test_modules(None)
.expect("failed to build app unit tests");

app.run_tests()
app.run_test_modules()
.expect("tests succeeded/failed when they should not have");
}

#[tokio::test]
async fn example_wasip3() {
let mut app = App::new("../examples/wasip3", "wasip3-example", None);
app.build(Some(&patched_go_path().await))
let mut app = App::new("../examples/wasip3", Some("wasip3-example"), None, true);
app.build_component(Some(&patched_go_path().await))
.expect("failed to build app");
app.run("/hello", "Hello, world!")
app.run_component("/hello", "Hello, world!")
.await
.expect("app failed to run");
}
Expand Down
Loading