Skip to content

Commit bc5dbc4

Browse files
committed
feat: add unit tests
Signed-off-by: Andrew Steurer <94206073+asteurer@users.noreply.github.com>
1 parent 51d7494 commit bc5dbc4

16 files changed

Lines changed: 719 additions & 245 deletions

File tree

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,3 @@ examples/*/go.mod
99
examples/*/go.sum
1010
examples/*/wasi_*
1111
examples/*/wit_*
12-
examples/*/export_wasi*

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,7 @@ You can download a specific release from the [release page](https://github.com/b
3737
```sh
3838
cargo install --git https://github.com/bytecodealliance/componentize-go
3939
```
40+
41+
## Usage
42+
43+
Please reference the `README.md` and `Makefile` files in each of the directories in [examples](./examples/).

examples/wasip2/Makefile

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
11
generate-bindings:
2-
componentize-go --world wasip2-example bindings --format
3-
go mod tidy
2+
@componentize-go --world wasip2-example bindings --format
3+
@go mod tidy
44

55
build-component: generate-bindings
6-
componentize-go --world wasip2-example componentize
6+
@componentize-go --world wasip2-example build
77

88
.PHONY: run
99
run: build-component
10-
wasmtime serve -Sp3,cli main.wasm
10+
@wasmtime serve -Sp3,cli main.wasm
11+
12+
build-tests: generate-bindings
13+
@componentize-go test --wasip1 --pkg ./unit_tests_should_pass --pkg ./unit_tests_should_fail
14+
15+
run-tests: build-tests
16+
@echo "===== Running tests that will pass ====="
17+
@wasmtime run test_unit_tests_should_pass.wasm
18+
@echo ""
19+
20+
@echo "===== Running tests that will fail ====="
21+
@wasmtime run test_unit_tests_should_fail.wasm || true
22+
@echo ""

examples/wasip2/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# `wasip2` Example
2+
23
## Usage
4+
35
### Prerequisites
6+
47
- [**componentize-go**](https://github.com/bytecodealliance/componentize-go) - Latest version
58
- [**go**](https://go.dev/dl/) - v1.25+
69
- [**wasmtime**](https://github.com/bytecodealliance/wasmtime) - v40.0.2
@@ -14,3 +17,7 @@ make run
1417
curl localhost:8080
1518
```
1619

20+
### Run unit tests
21+
```sh
22+
make run-tests
23+
```
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package unit_tests_should_fail
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func test_sum(a, b int) int {
8+
return a + b
9+
}
10+
11+
func TestSum(t *testing.T) {
12+
tests := []struct {
13+
name string
14+
a, b int
15+
expected int
16+
}{
17+
{"positive numbers", 2, 3, -1},
18+
{"negative numbers", -1, -2, 10},
19+
{"zeros", 0, 0, 5},
20+
}
21+
22+
for _, tt := range tests {
23+
t.Run(tt.name, func(t *testing.T) {
24+
got := test_sum(tt.a, tt.b)
25+
if got != tt.expected {
26+
t.Errorf("test_sum(%d, %d) = %d, expected %d", tt.a, tt.b, got, tt.expected)
27+
}
28+
})
29+
}
30+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package unit_tests_should_pass
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func test_sum(a, b int) int {
8+
return a + b
9+
}
10+
11+
func TestSum(t *testing.T) {
12+
tests := []struct {
13+
name string
14+
a, b int
15+
expected int
16+
}{
17+
{"positive numbers", 2, 3, 5},
18+
{"negative numbers", -1, -2, -3},
19+
{"zeros", 0, 0, 0},
20+
}
21+
22+
for _, tt := range tests {
23+
t.Run(tt.name, func(t *testing.T) {
24+
got := test_sum(tt.a, tt.b)
25+
if got != tt.expected {
26+
t.Errorf("test_sum(%d, %d) = %d, expected %d", tt.a, tt.b, got, tt.expected)
27+
}
28+
})
29+
}
30+
}

src/bindings.rs renamed to src/cmd_bindings.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::common::{make_path_absolute, parse_wit};
1+
use crate::utils::{make_path_absolute, parse_wit};
22
use anyhow::Result;
33
use std::path::{Path, PathBuf};
44

src/cmd_build.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
use crate::utils::{check_go_version, make_path_absolute};
2+
use anyhow::{Result, anyhow};
3+
use std::{path::PathBuf, process::Command};
4+
5+
/// Compiles a Go application to a wasm module with `go build`.
6+
///
7+
/// If the module is not going to be adapted to the component model,
8+
/// set the `only_wasip1` arg to true.
9+
pub fn build_module(
10+
go_module: Option<&PathBuf>,
11+
out: Option<&PathBuf>,
12+
go_path: Option<&PathBuf>,
13+
only_wasip1: bool,
14+
) -> Result<PathBuf> {
15+
let go = match &go_path {
16+
Some(p) => make_path_absolute(p)?,
17+
None => PathBuf::from("go"),
18+
};
19+
20+
check_go_version(&go)?;
21+
22+
let out_path_buf = match &out {
23+
Some(p) => make_path_absolute(p)?,
24+
None => std::env::current_dir()?.join("main.wasm"),
25+
};
26+
27+
// Ensuring the newly compiled wasm file overwrites any previously-existing wasm file
28+
if out_path_buf.exists() {
29+
std::fs::remove_file(&out_path_buf)?;
30+
}
31+
32+
let out_path = out_path_buf
33+
.to_str()
34+
.ok_or_else(|| anyhow!("Output path is not valid unicode"))?;
35+
36+
let module_path = match &go_module {
37+
Some(p) => {
38+
if !p.is_dir() {
39+
return Err(anyhow!("Module path '{}' is not a directory", p.display()));
40+
}
41+
p.to_str()
42+
.ok_or_else(|| anyhow!("Module path is not valid unicode"))?
43+
}
44+
None => ".",
45+
};
46+
47+
// TODO: for when/if we decide to allow users to build wasm modules without componentizing them
48+
#[allow(unused_variables)]
49+
let module_args = [
50+
"build",
51+
"-C",
52+
module_path,
53+
"-buildmode=c-shared",
54+
"-o",
55+
out_path,
56+
];
57+
58+
let component_args = [
59+
"build",
60+
"-C",
61+
module_path,
62+
"-buildmode=c-shared",
63+
"-ldflags=-checklinkname=0",
64+
"-o",
65+
out_path,
66+
];
67+
68+
let output = if only_wasip1 {
69+
unimplemented!();
70+
// TODO: for when/if we decide to allow users to build wasm modules without componentizing them
71+
#[allow(unreachable_code)]
72+
Command::new(&go)
73+
.args(module_args)
74+
.env("GOOS", "wasip1")
75+
.env("GOARCH", "wasm")
76+
.output()?
77+
} else {
78+
Command::new(&go)
79+
.args(component_args)
80+
.env("GOOS", "wasip1")
81+
.env("GOARCH", "wasm")
82+
.output()?
83+
};
84+
85+
if !output.status.success() {
86+
return Err(anyhow!(
87+
"'go build' command failed: {}",
88+
String::from_utf8_lossy(&output.stderr)
89+
));
90+
}
91+
92+
Ok(PathBuf::from(out_path))
93+
}

src/cmd_test.rs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
use crate::utils::{check_go_version, make_path_absolute};
2+
use anyhow::{Result, anyhow};
3+
use std::{
4+
path::{Path, PathBuf},
5+
process::Command,
6+
};
7+
8+
/// Compiles a Go application to a wasm module with `go test -c`.
9+
///
10+
/// If the module is not going to be adapted to the component model,
11+
/// set the `only_wasip1` arg to true.
12+
pub fn build_test_module(
13+
path: &Path,
14+
output_dir: Option<&PathBuf>,
15+
go_path: Option<&PathBuf>,
16+
only_wasip1: bool,
17+
) -> Result<PathBuf> {
18+
let go = match &go_path {
19+
Some(p) => make_path_absolute(p)?,
20+
None => PathBuf::from("go"),
21+
};
22+
23+
check_go_version(&go)?;
24+
25+
let test_wasm_path = {
26+
// The directory in which the test component will be placed
27+
let test_dir = match output_dir {
28+
Some(p) => make_path_absolute(p)?,
29+
None => std::env::current_dir()?,
30+
};
31+
32+
test_dir.join(get_test_filename(path))
33+
};
34+
35+
// Ensuring the newly compiled wasm file overwrites any previously-existing wasm file
36+
if test_wasm_path.exists() {
37+
std::fs::remove_file(&test_wasm_path)?;
38+
}
39+
40+
if let Some(dir) = output_dir {
41+
std::fs::create_dir_all(dir)?;
42+
}
43+
44+
// The -buildmode flag mutes the unit test output, so it is ommitted
45+
let module_args = [
46+
"test",
47+
"-c",
48+
"-ldflags=-checklinkname=0",
49+
"-o",
50+
test_wasm_path
51+
.to_str()
52+
.expect("the combined paths of 'output-dir' and 'pkg' are not valid unicode"),
53+
path.to_str().expect("pkg path is not valid unicode"),
54+
];
55+
56+
// TODO: for when we figure out how wasip2 tests are to be run
57+
#[allow(unused_variables)]
58+
let component_args = [
59+
"test",
60+
"-c",
61+
"-buildmode=c-shared",
62+
"-ldflags=-checklinkname=0",
63+
"-o",
64+
test_wasm_path
65+
.to_str()
66+
.expect("the combined paths of 'output-dir' and 'pkg' are not valid unicode"),
67+
path.to_str().expect("pkg path is not valid unicode"),
68+
];
69+
70+
let output = if only_wasip1 {
71+
Command::new(&go)
72+
.args(module_args)
73+
.env("GOOS", "wasip1")
74+
.env("GOARCH", "wasm")
75+
.output()?
76+
} else {
77+
unimplemented!();
78+
79+
// TODO: for when we figure out how wasip2 tests are to be run
80+
#[allow(unreachable_code)]
81+
Command::new(&go)
82+
.args(component_args)
83+
.env("GOOS", "wasip1")
84+
.env("GOARCH", "wasm")
85+
.output()?
86+
};
87+
88+
if !output.status.success() {
89+
return Err(anyhow!(
90+
"'go test -c' command failed: {}",
91+
String::from_utf8_lossy(&output.stderr)
92+
));
93+
}
94+
95+
Ok(test_wasm_path)
96+
}
97+
98+
// Format the test filename based on the package path (see unit tests for more details).
99+
pub fn get_test_filename(path: &Path) -> String {
100+
let components: Vec<&str> = path
101+
.components()
102+
.filter_map(|c| match c {
103+
// Filter out the `/` and `.`
104+
std::path::Component::Normal(s) => s.to_str(),
105+
_ => None,
106+
})
107+
.collect();
108+
109+
let tail = if components.len() >= 2 {
110+
&components[components.len() - 2..]
111+
} else {
112+
&components[..]
113+
};
114+
115+
format!("test_{}.wasm", tail.join("_"))
116+
}
117+
118+
#[cfg(test)]
119+
mod tests {
120+
use super::*;
121+
122+
#[test]
123+
fn test_get_test_filename() {
124+
let tests = [
125+
("./foo/bar/baz", "test_bar_baz.wasm"),
126+
("./foo/bar", "test_foo_bar.wasm"),
127+
("./bar", "test_bar.wasm"),
128+
("/usr/bin/foo/bar/baz", "test_bar_baz.wasm"),
129+
];
130+
131+
for (input, expected) in tests.iter() {
132+
let input_string = input.to_string();
133+
let actual = get_test_filename(&PathBuf::from(input_string));
134+
assert_eq!(actual, expected.to_string());
135+
}
136+
}
137+
}

0 commit comments

Comments
 (0)