Skip to content

Commit

Permalink
Adds ConsoleLog and tests in a support matrix (#8)
Browse files Browse the repository at this point in the history
* Adds ConsoleLog and tests in a support matrix

This adds `wapc.ConsoleLog` and stubs in the first end-to-end test for
guest integration using wazero.

This adds CI, reverse engineering a support matrix by the min and max
supported Go versions based on latest TinyGo.

Note: This is wider than Go's policy as Go no longer supports 1.16 which
is the version required by TinyGo 0.17.0.

Signed-off-by: Adrian Cole <[email protected]>

* typo

Signed-off-by: Adrian Cole <[email protected]>

Signed-off-by: Adrian Cole <[email protected]>
  • Loading branch information
codefromthecrypt authored Aug 18, 2022
1 parent 0b8458c commit 7611a8a
Show file tree
Hide file tree
Showing 12 changed files with 295 additions and 4 deletions.
49 changes: 49 additions & 0 deletions .github/workflows/go-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: go tests

on:
push:
tags:
- v*
branches:
- master
- main
pull_request:

jobs:
# Note: TinyGo is not idempotent when generating wasm, so we don't check in
# %.wasm as a part of this job.
build:
runs-on: ubuntu-latest
strategy:
matrix:
go-version: # Note: Go only supports 2 versions: https://go.dev/doc/devel/release#policy
- "1.16" # Minimum Go version of latest TinyGo
- "1.18" # Latest
tinygo-version: # See https://github.com/tinygo-org/tinygo/releases
- "0.18.0" # First version to use wasi_snapshot_preview1
- "0.25.0" # Latest

steps:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.18

- name: Install TinyGo
run: | # Installing via curl so commands are similar on OS/x
tinygo_version=${{ matrix.tinygo-version }}
curl -sSL https://github.com/tinygo-org/tinygo/releases/download/v${tinygo_version}/tinygo${tinygo_version}.linux-amd64.tar.gz | sudo tar -C /usr/local -xzf -
echo "TINYGOROOT=/usr/local/tinygo" >> $GITHUB_ENV
echo "/usr/local/tinygo/bin" >> $GITHUB_PATH
- name: Checkout
uses: actions/checkout@v3

- name: Build example
run: tinygo build -o example/hello.wasm -scheduler=none --no-debug -target=wasi example/hello.go

- name: Build test wasm
run: cd internal; make build-wasm

- name: Test
run: go test -v ./...
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func hello(payload []byte) ([]byte, error) {
```

```sh
tinygo build -o example/hello.wasm -target wasm -no-debug example/hello.go
tinygo build -o example/hello.wasm -scheduler=none --no-debug -target=wasi example/hello.go
```

## Considerations
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/wapc/wapc-guest-tinygo

go 1.15
go 1.16
3 changes: 3 additions & 0 deletions imports.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build !purego && !appengine && !wasm && !tinygo.wasm && !wasi
// +build !purego,!appengine,!wasm,!tinygo.wasm,!wasi

package wapc
Expand All @@ -23,3 +24,5 @@ func hostResponse(ptr uintptr) {}
func hostErrorLen() uint32 { return 0 }

func hostError(ptr uintptr) {}

func consoleLog(ptr uintptr, size uint32) {}
5 changes: 5 additions & 0 deletions imports_webassembly.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build wasm || tinygo.wasm || wasi
// +build wasm tinygo.wasm wasi

package wapc
Expand Down Expand Up @@ -37,3 +38,7 @@ func hostErrorLen() uint32
//go:wasm-module wapc
//go:export __host_error
func hostError(ptr uintptr)

//go:wasm-module wapc
//go:export __console_log
func consoleLog(ptr uintptr, size uint32)
9 changes: 9 additions & 0 deletions internal/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
tinygo_sources := $(wildcard testdata/*/*.go)
.PHONY: build.wasm
build-wasm: $(tinygo_sources)
@echo "------------------"
@echo "Building Test Wasm"
@echo "------------------"
@for f in $^; do \
tinygo build -o $$(echo $$f | sed -e 's/\.go/\.wasm/') -scheduler=none --no-debug -target=wasi $$f; \
done
67 changes: 67 additions & 0 deletions internal/e2e_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package internal_test

import (
"context"
_ "embed"
"github.com/stretchr/testify/require"
"github.com/tetratelabs/wazero/api"
"testing"

"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/wasi_snapshot_preview1"
)

// testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors.
var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")

// consoleLogWasm was compiled from testdata/__console_log/main.go
//
//go:embed testdata/__console_log/main.wasm
var consoleLogWasm []byte

func Test_EndToEnd(t *testing.T) {
type testCase struct {
name string
guest []byte
test func(t *testing.T, guest api.Module, host *wapcHost)
}

tests := []testCase{
{
name: "ConsoleLog",
guest: consoleLogWasm,
test: func(t *testing.T, guest api.Module, host *wapcHost) {
// main invokes ConsoleLog
require.Equal(t, []string{"msg", "msg1", "msg"}, host.consoleLogMessages)
},
},
}

// Create a new WebAssembly Runtime.
r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfig().
// WebAssembly 2.0 allows use of any version of TinyGo, including 0.24+.
WithWasmCore2())
defer r.Close(testCtx) // This closes everything this Runtime created.

// Instantiate WASI, which implements system I/O such as console output and
// is required for `tinygo build -target=wasi`
if _, err := wasi_snapshot_preview1.Instantiate(testCtx, r); err != nil {
t.Errorf("Error instantiating WASI - %v", err)
}

for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
h, host := instantiateWapcHost(t, r)
defer host.Close(testCtx)

g, err := r.InstantiateModuleFromBinary(testCtx, tc.guest)
if err != nil {
t.Errorf("Error instantiating waPC guest - %v", err)
}
defer g.Close(testCtx)

tc.test(t, g, h)
})
}
}
16 changes: 16 additions & 0 deletions internal/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module github.com/wapc/wapc-guest-tinygo/internal

go 1.18

require (
github.com/stretchr/testify v1.8.0
github.com/tetratelabs/wazero v0.0.0-20220816233340-7d071a45d786
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

replace github.com/wapc/wapc-guest-tinygo => ../
17 changes: 17 additions & 0 deletions internal/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/tetratelabs/wazero v0.0.0-20220816233340-7d071a45d786 h1:EycFERct5dJumsQUtx/HMpgEAAs348zwdS+aKtBT1eg=
github.com/tetratelabs/wazero v0.0.0-20220816233340-7d071a45d786/go.mod h1:CD5smBN5rGZo7UNe8aUiWyYE3bDWED/CQSonog9NSEg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
113 changes: 113 additions & 0 deletions internal/host_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package internal_test

import (
"context"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
"testing"
)

// instantiateWapcHost instantiates a test waPC host and returns it and a cleanup function.
func instantiateWapcHost(t *testing.T, r wazero.Runtime) (*wapcHost, api.Closer) {
h := &wapcHost{t: t}
// Export host functions (in the order defined in https://wapc.io/docs/spec/#required-host-exports)
if host, err := r.NewModuleBuilder("wapc").
ExportFunction("__host_call", h.hostCall,
"__host_call", "bind_ptr", "bind_len", "ns_ptr", "ns_len", "cmd_ptr", "cmd_len", "payload_ptr", "payload_len").
ExportFunction("__console_log", h.consoleLog,
"__console_log", "ptr", "len").
ExportFunction("__guest_request", h.guestRequest,
"__guest_request", "op_ptr", "ptr").
ExportFunction("__host_response", h.hostResponse,
"__host_response", "ptr").
ExportFunction("__host_response_len", h.hostResponseLen,
"__host_response_len").
ExportFunction("__guest_response", h.guestResponse,
"__guest_response", "ptr", "len").
ExportFunction("__guest_error", h.guestError,
"__guest_error", "ptr", "len").
ExportFunction("__host_error", h.hostError,
"__host_error", "ptr").
ExportFunction("__host_error_len", h.hostErrorLen,
"__host_error_len").
Instantiate(testCtx, r); err != nil {
t.Errorf("Error instantiating waPC host - %v", err)
return h, nil
} else {
return h, host
}
}

type wapcHost struct {
t *testing.T
consoleLogMessages []string
}

// hostCall is the WebAssembly function export "__host_call", which initiates a host using the callHandler using
// parameters read from linear memory (wasm.Memory).
func (w *wapcHost) hostCall(ctx context.Context, m api.Module, bindPtr, bindLen, nsPtr, nsLen, cmdPtr, cmdLen, payloadPtr, payloadLen uint32) int32 {
panic("TODO")
}

// consoleLog is the WebAssembly function export "__console_log", which logs the message stored by the guest at the
// given offset (ptr) and length (len) in linear memory (wasm.Memory).
func (w *wapcHost) consoleLog(ctx context.Context, m api.Module, ptr, len uint32) {
msg := w.requireReadString(ctx, m.Memory(), "msg", ptr, len)
w.consoleLogMessages = append(w.consoleLogMessages, msg)
}

// guestRequest is the WebAssembly function export "__guest_request", which writes the invokeContext.operation and
// invokeContext.guestReq to the given offsets (opPtr, ptr) in linear memory (wasm.Memory).
func (w *wapcHost) guestRequest(ctx context.Context, m api.Module, opPtr, ptr uint32) {
panic("TODO")
}

// hostResponse is the WebAssembly function export "__host_response", which writes the invokeContext.hostResp to the
// given offset (ptr) in linear memory (wasm.Memory).
func (w *wapcHost) hostResponse(ctx context.Context, m api.Module, ptr uint32) {
panic("TODO")
}

// hostResponse is the WebAssembly function export "__host_response_len", which returns the length of the current host
// response from invokeContext.hostResp.
func (w *wapcHost) hostResponseLen(ctx context.Context) uint32 {
panic("TODO")
}

// guestResponse is the WebAssembly function export "__guest_response", which reads invokeContext.guestResp from the
// given offset (ptr) and length (len) in linear memory (wasm.Memory).
func (w *wapcHost) guestResponse(ctx context.Context, m api.Module, ptr, len uint32) {
panic("TODO")
}

// guestError is the WebAssembly function export "__guest_error", which reads invokeContext.guestErr from the given
// offset (ptr) and length (len) in linear memory (wasm.Memory).
func (w *wapcHost) guestError(ctx context.Context, m api.Module, ptr, len uint32) {
panic("TODO")
}

// hostError is the WebAssembly function export "__host_error", which writes the invokeContext.hostErr to the given
// offset (ptr) in linear memory (wasm.Memory).
func (w *wapcHost) hostError(ctx context.Context, m api.Module, ptr uint32) {
panic("TODO")
}

// hostError is the WebAssembly function export "__host_error_len", which returns the length of the current host error
// from invokeContext.hostErr.
func (w *wapcHost) hostErrorLen(ctx context.Context) uint32 {
panic("TODO")
}

// requireReadString is a convenience function that casts requireRead
func (w *wapcHost) requireReadString(ctx context.Context, mem api.Memory, fieldName string, offset, byteCount uint32) string {
return string(w.requireRead(ctx, mem, fieldName, offset, byteCount))
}

// requireRead is like api.Memory except that it panics if the offset and byteCount are out of range.
func (w *wapcHost) requireRead(ctx context.Context, mem api.Memory, fieldName string, offset, byteCount uint32) []byte {
buf, ok := mem.Read(ctx, offset, byteCount)
if !ok {
w.t.Fatalf("out of memory reading %s", fieldName)
}
return buf
}
7 changes: 7 additions & 0 deletions internal/testdata/__console_log/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

func main() {
wapc.ConsoleLog("msg")
wapc.ConsoleLog("msg1")
wapc.ConsoleLog("msg")
}
9 changes: 7 additions & 2 deletions wapc.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ var (
allFunctions = Functions{}
)

// RegisterFunctions adds functions by name to the registery.
// RegisterFunctions adds functions by name to the registry.
// This should be invoked in `main()`.
func RegisterFunctions(functions Functions) {
for name, fn := range functions {
allFunctions[name] = fn
}
}

// RegisterFunction adds a single function by name to the registery.
// RegisterFunction adds a single function by name to the registry.
// This should be invoked in `main()`.
func RegisterFunction(name string, fn Function) {
allFunctions[name] = fn
Expand Down Expand Up @@ -62,6 +62,11 @@ func guestCall(operationSize uint32, payloadSize uint32) bool {
return false
}

// ConsoleLog writes the message the underlying waPC console logger.
func ConsoleLog(msg string) {
consoleLog(stringToPointer(msg), uint32(len(msg)))
}

// HostCall invokes an operation on the host. The host uses `namespace` and `operation`
// to route to the `payload` to the appropriate operation. The host will return
// a response payload if successful.
Expand Down

0 comments on commit 7611a8a

Please sign in to comment.