Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • elixxir/xxdk-wasm
1 result
Show changes
Commits on Source (4)
......@@ -7,7 +7,7 @@ require (
github.com/hack-pad/go-indexeddb v0.2.0
github.com/pkg/errors v0.9.1
github.com/spf13/jwalterweatherman v1.1.0
gitlab.com/elixxir/client/v4 v4.6.3
gitlab.com/elixxir/client/v4 v4.6.2-0.20230511215110-b43e18a47875
gitlab.com/elixxir/crypto v0.0.7-0.20230413162806-a99ec4bfea32
gitlab.com/elixxir/primitives v0.0.3-0.20230214180039-9a25e2d3969c
gitlab.com/xx_network/crypto v0.0.5-0.20230214003943-8a09396e95dd
......@@ -22,6 +22,7 @@ require (
github.com/badoux/checkmail v1.2.1 // indirect
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
github.com/cloudflare/circl v1.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
github.com/elliotchance/orderedmap v1.4.0 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
......@@ -48,6 +49,7 @@ require (
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.2 // indirect
github.com/pkg/profile v1.6.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rs/cors v1.8.2 // indirect
github.com/sethvargo/go-diceware v0.3.0 // indirect
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
......@@ -57,6 +59,7 @@ require (
github.com/spf13/cobra v1.7.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.12.0 // indirect
github.com/stretchr/testify v1.8.2 // indirect
github.com/subosito/gotenv v1.4.0 // indirect
github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 // indirect
github.com/ttacon/libphonenumber v1.2.1 // indirect
......@@ -64,7 +67,7 @@ require (
github.com/zeebo/blake3 v0.2.3 // indirect
gitlab.com/elixxir/bloomfilter v0.0.0-20230315224936-a4459418f300 // indirect
gitlab.com/elixxir/comms v0.0.4-0.20230310205528-f06faa0d2f0b // indirect
gitlab.com/elixxir/ekv v0.2.1 // indirect
gitlab.com/elixxir/ekv v0.3.1-0.20230504190918-f5e96603c2e0 // indirect
gitlab.com/xx_network/comms v0.0.4-0.20230214180029-5387fb85736d // indirect
gitlab.com/xx_network/ring v0.0.3-0.20220902183151-a7d3b15bc981 // indirect
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
......
......@@ -513,6 +513,8 @@ gitlab.com/elixxir/client/v4 v4.6.2-0.20230413171204-002612660098 h1:bdwXgEa0i9K
gitlab.com/elixxir/client/v4 v4.6.2-0.20230413171204-002612660098/go.mod h1:G+lN+LvQPGcm5BQnrhnqT1xiRIAzH3OffAM+5oI9SUg=
gitlab.com/elixxir/client/v4 v4.6.2-0.20230425190953-cd51598e9245 h1:pBwoSYD+BFIr5Wyc+PQhqm+fZGsRSNXCpF0z1cQQzK8=
gitlab.com/elixxir/client/v4 v4.6.2-0.20230425190953-cd51598e9245/go.mod h1:G+lN+LvQPGcm5BQnrhnqT1xiRIAzH3OffAM+5oI9SUg=
gitlab.com/elixxir/client/v4 v4.6.2-0.20230511215110-b43e18a47875 h1:u9DlU8xAk0rTvguhWK+6D/MfLHdR+jlDTfGJjiszJDE=
gitlab.com/elixxir/client/v4 v4.6.2-0.20230511215110-b43e18a47875/go.mod h1:dLKU2zSWrZLk/fomAtt1DFGgpTHQAfPdxdXNp3EtRZU=
gitlab.com/elixxir/client/v4 v4.6.3 h1:oUsm5cn2Vnfqz+xwGYKrqFkPNN3sDAyp00EPGhUIA5E=
gitlab.com/elixxir/client/v4 v4.6.3/go.mod h1:G+lN+LvQPGcm5BQnrhnqT1xiRIAzH3OffAM+5oI9SUg=
gitlab.com/elixxir/comms v0.0.4-0.20230310205528-f06faa0d2f0b h1:8AVK93UEs/aufoqtFgyMVt9gf0oJ8F4pA60ZvEVvG+s=
......@@ -521,6 +523,8 @@ gitlab.com/elixxir/crypto v0.0.7-0.20230413162806-a99ec4bfea32 h1:Had0F7rMPgJJ2B
gitlab.com/elixxir/crypto v0.0.7-0.20230413162806-a99ec4bfea32/go.mod h1:/SLOlvkYVVJf6IU+vEjMLnS7cjjcoTlPV45g6tv6INc=
gitlab.com/elixxir/ekv v0.2.1 h1:dtwbt6KmAXG2Tik5d60iDz2fLhoFBgWwST03p7T+9Is=
gitlab.com/elixxir/ekv v0.2.1/go.mod h1:USLD7xeDnuZEavygdrgzNEwZXeLQJK/w1a+htpN+JEU=
gitlab.com/elixxir/ekv v0.3.1-0.20230504190918-f5e96603c2e0 h1:4d2vg4Sh3N5mR1ta152cg6ybPWHYqsPtkEyJKaDYGnw=
gitlab.com/elixxir/ekv v0.3.1-0.20230504190918-f5e96603c2e0/go.mod h1:EMaUQrsOxvEPQ0/8V/PSkGqFmEC2axBG/uqY0oW2uJM=
gitlab.com/elixxir/primitives v0.0.3-0.20230214180039-9a25e2d3969c h1:muG8ff95woeVVwQoJHCEclxBFB22lc7EixPylEkYDRU=
gitlab.com/elixxir/primitives v0.0.3-0.20230214180039-9a25e2d3969c/go.mod h1:phun4PLkHJA6wcL4JIhhxZztrmCyJHWPNppBP3DUD2Y=
gitlab.com/xx_network/comms v0.0.4-0.20230214180029-5387fb85736d h1:AZf2h0fxyO1KxhZPP9//jG3Swb2BcuKbxtNXJgooLss=
......
......@@ -230,6 +230,12 @@ func setGlobals() {
js.Global().Set("TransmitSingleUse", js.FuncOf(wasm.TransmitSingleUse))
js.Global().Set("Listen", js.FuncOf(wasm.Listen))
// wasm/sync.go
js.Global().Set("NewFileSystemRemoteStorage",
js.FuncOf(wasm.NewFileSystemRemoteStorage))
js.Global().Set("NewOrLoadSyncRemoteKV",
js.FuncOf(wasm.NewOrLoadSyncRemoteKV))
// wasm/timeNow.go
js.Global().Set("SetTimeSource", js.FuncOf(wasm.SetTimeSource))
js.Global().Set("SetOffset", js.FuncOf(wasm.SetOffset))
......
......@@ -106,3 +106,21 @@ func Await(awaitable js.Value) (result []js.Value, err []js.Value) {
return nil, err
}
}
// RunAndCatch runs the specified function and catches any errors thrown by
// Javascript. The errors should be of type Error.
func RunAndCatch(fn func() js.Value) (js.Value, error) {
var err error
defer func() {
if r := recover(); r != nil {
switch x := r.(type) {
case js.Value:
err = js.Error{Value: x}
default:
panic(r)
}
}
}()
return fn(), err
}
////////////////////////////////////////////////////////////////////////////////
// Copyright © 2022 xx foundation //
// //
// Use of this source code is governed by a license that can be found in the //
// LICENSE file. //
////////////////////////////////////////////////////////////////////////////////
//go:build js && wasm
package wasm
import (
"syscall/js"
"gitlab.com/elixxir/client/v4/bindings"
"gitlab.com/elixxir/xxdk-wasm/utils"
)
// TODO: add tests
////////////////////////////////////////////////////////////////////////////////
// Remote Storage Interface and Implementation(s) //
////////////////////////////////////////////////////////////////////////////////
// RemoteStoreFileSystem wraps the [bindings.RemoteStoreFileSystem] object so
// its methods can be wrapped to be Javascript compatible.
type RemoteStoreFileSystem struct {
api *bindings.RemoteStoreFileSystem
}
// newRemoteStoreFileSystemJS creates a new Javascript compatible object
// (map[string]any) that matches the [RemoteStoreFileSystem] structure.
func newRemoteStoreFileSystemJS(api *bindings.RemoteStoreFileSystem) map[string]any {
rsf := RemoteStoreFileSystem{api}
rsfMap := map[string]any{
"Read": js.FuncOf(rsf.Read),
"Write": js.FuncOf(rsf.Write),
"GetLastModified": js.FuncOf(rsf.GetLastModified),
"GetLastWrite": js.FuncOf(rsf.GetLastWrite),
}
return rsfMap
}
// NewFileSystemRemoteStorage is a constructor for [RemoteStoreFileSystem].
//
// Parameters:
// - args[0] - The base directory that all file operations will be performed.
// It must contain a file delimiter (i.e., `/`) (string).
//
// Returns:
// - A Javascript representation of the [RemoteStoreFileSystem] object.
func NewFileSystemRemoteStorage(_ js.Value, args []js.Value) any {
baseDir := args[0].String()
api := bindings.NewFileSystemRemoteStorage(baseDir)
return newRemoteStoreFileSystemJS(api)
}
// Read reads from the provided file path and returns the data at that path.
// An error is returned if it failed to read the file.
//
// Parameters:
// - args[0] - The file path to read from (string).
//
// Returns a promise:
// - Resolves to the file data (Uint8Array)
// - Rejected with an error if reading from the file fails.
func (rsf *RemoteStoreFileSystem) Read(_ js.Value, args []js.Value) any {
path := args[0].String()
promiseFn := func(resolve, reject func(args ...any) js.Value) {
data, err := rsf.api.Read(path)
if err != nil {
reject(utils.JsTrace(err))
} else {
resolve(utils.CopyBytesToJS(data))
}
}
return utils.CreatePromise(promiseFn)
}
// Write writes to the file path the provided data. An error is returned if it
// fails to write to file.
//
// Parameters:
// - args[0] - The file path to write to (string).
// - args[1] - The file data to write (Uint8Array).
//
// Returns a promise:
// - Resolves on success (void).
// - Rejected with an error if writing to the file fails.
func (rsf *RemoteStoreFileSystem) Write(_ js.Value, args []js.Value) any {
path := args[0].String()
data := utils.CopyBytesToGo(args[1])
promiseFn := func(resolve, reject func(args ...any) js.Value) {
err := rsf.api.Write(path, data)
if err != nil {
reject(utils.JsTrace(err))
} else {
resolve()
}
}
return utils.CreatePromise(promiseFn)
}
// GetLastModified returns when the file at the given file path was last
// modified. If the implementation that adheres to this interface does not
// support this, [Write] or [Read] should be implemented to either write a
// separate timestamp file or add a prefix.
//
// Parameters:
// - args[0] - The file path (string).
//
// Returns a promise:
// - Resolves to the JSON of [bindings.RemoteStoreReport] (Uint8Array).
// - Rejected with an error on failure.
func (rsf *RemoteStoreFileSystem) GetLastModified(_ js.Value, args []js.Value) any {
path := args[0].String()
promiseFn := func(resolve, reject func(args ...any) js.Value) {
report, err := rsf.api.GetLastModified(path)
if err != nil {
reject(utils.JsTrace(err))
} else {
resolve(utils.CopyBytesToJS(report))
}
}
return utils.CreatePromise(promiseFn)
}
// GetLastWrite retrieves the most recent successful write operation that was
// received by [RemoteStoreFileSystem].
//
// Returns a promise:
// - Resolves to the JSON of [bindings.RemoteStoreReport] (Uint8Array).
// - Rejected with an error on failure.
func (rsf *RemoteStoreFileSystem) GetLastWrite(js.Value, []js.Value) any {
promiseFn := func(resolve, reject func(args ...any) js.Value) {
report, err := rsf.api.GetLastWrite()
if err != nil {
reject(utils.JsTrace(err))
} else {
resolve(utils.CopyBytesToJS(report))
}
}
return utils.CreatePromise(promiseFn)
}
////////////////////////////////////////////////////////////////////////////////
// RemoteKV Methods //
////////////////////////////////////////////////////////////////////////////////
// RemoteKV wraps the [bindings.RemoteKV] object so its methods can be wrapped
// to be Javascript compatible.
type RemoteKV struct {
api *bindings.RemoteKV
}
// newRemoteKvJS creates a new Javascript compatible object (map[string]any)
// that matches the [RemoteKV] structure.
func newRemoteKvJS(api *bindings.RemoteKV) map[string]any {
rkv := RemoteKV{api}
rkvMap := map[string]any{
"Write": js.FuncOf(rkv.Write),
"Read": js.FuncOf(rkv.Read),
"GetList": js.FuncOf(rkv.GetList),
}
return rkvMap
}
// NewOrLoadSyncRemoteKV constructs a [RemoteKV].
//
// Parameters:
// - args[0] - ID of [E2e] object in tracker (int).
// - args[1] - A Javascript object that implements the functions on
// [RemoteKVCallbacks]. These will be the callbacks that are called for
// [bindings.RemoteStore] operations.
// - args[2] - A [RemoteStoreCallbacks]. This will be a structure the consumer
// implements. This acts as a wrapper around the remote storage API
// (e.g., Google Drive's API, DropBox's API, etc.).
//
// Returns a promise:
// - Resolves to a Javascript representation of the [RemoteKV] object.
// - Rejected with an error if initialising the remote KV fails.
func NewOrLoadSyncRemoteKV(_ js.Value, args []js.Value) any {
e2eID := args[0].Int()
remoteKvCallbacks := newRemoteKVCallbacks(args[1])
remote := newRemoteStoreCallbacks(args[2])
promiseFn := func(resolve, reject func(args ...any) js.Value) {
api, err :=
bindings.NewOrLoadSyncRemoteKV(e2eID, remoteKvCallbacks, remote)
if err != nil {
reject(utils.JsTrace(err))
} else {
resolve(newRemoteKvJS(api))
}
}
return utils.CreatePromise(promiseFn)
}
// Write writes a transaction to the remote and local store.
//
// Parameters:
// - args[0] - The key that this data will be written to (i.e., the device
// name, the channel name, etc.). Certain keys should follow a pattern and
// contain special characters (see [RemoteKV.GetList] for details) (string).
// - args[1] - The data that will be stored (i.e., state data) (Uint8Array).
// - args[2] - A Javascript object that implements the functions on
// [RemoteKVCallbacks]. This may be nil if you do not care about the network
// report.
//
// Returns a promise:
// - Resolves on success (void).
// - Rejected with an error if writing to the file fails.
func (rkv *RemoteKV) Write(_ js.Value, args []js.Value) any {
path := args[0].String()
data := utils.CopyBytesToGo(args[1])
cb := newRemoteKVCallbacks(args[1])
promiseFn := func(resolve, reject func(args ...any) js.Value) {
err := rkv.api.Write(path, data, cb)
if err != nil {
reject(utils.JsTrace(err))
} else {
resolve()
}
}
return utils.CreatePromise(promiseFn)
}
// Read retrieves the data stored in the underlying KV. Returns an error if the
// data at this key cannot be retrieved.
//
// Parameters:
// - args[0] - The path that this data will be written to (i.e., the device
// name) (string).
//
// Returns a promise:
// - Resolves to the file data (Uint8Array)
// - Rejected with an error if reading from the file fails.
func (rkv *RemoteKV) Read(_ js.Value, args []js.Value) any {
path := args[0].String()
promiseFn := func(resolve, reject func(args ...any) js.Value) {
data, err := rkv.api.Read(path)
if err != nil {
reject(utils.JsTrace(err))
} else {
resolve(utils.CopyBytesToJS(data))
}
}
return utils.CreatePromise(promiseFn)
}
// GetList returns all entries for a path (or key) that contain the name
// parameter from the local store.
//
// For example, assuming the usage of the [sync.LocalStoreKeyDelimiter], if both
// "channels-123" and "channels-abc" are written to [RemoteKV], then
// GetList("channels") will retrieve the data for both channels. All data that
// contains no [sync.LocalStoreKeyDelimiter] can be retrieved using GetList("").
//
// Parameters:
// - args[0] - Some prefix to a Write operation. If no prefix applies, simply
// use the empty string. (string).
//
// Returns:
// - The file data (Uint8Array)
// - Throws a TypeError if getting the list fails.
func (rkv *RemoteKV) GetList(_ js.Value, args []js.Value) any {
name := args[0].String()
data, err := rkv.api.GetList(name)
if err != nil {
utils.Throw(utils.TypeError, err)
return nil
}
return utils.CopyBytesToJS(data)
}
// RemoteStoreCallbacks wraps Javascript callbacks to adhere to the
// [bindings.RemoteStore] interface.
type RemoteStoreCallbacks struct {
read func(args ...any) js.Value
write func(args ...any) js.Value
getLastModified func(args ...any) js.Value
getLastWrite func(args ...any) js.Value
}
// newRemoteStoreCallbacks maps the functions of the Javascript object matching
// [bindings.RemoteStore] to a RemoteStoreCallbacks.
func newRemoteStoreCallbacks(arg js.Value) *RemoteStoreCallbacks {
return &RemoteStoreCallbacks{
read: utils.WrapCB(arg, "Read"),
write: utils.WrapCB(arg, "Write"),
getLastModified: utils.WrapCB(arg, "GetLastModified"),
getLastWrite: utils.WrapCB(arg, "GetLastWrite"),
}
}
// Read reads from the provided file path and returns the data at that path.
// An error is returned if it failed to read the file.
//
// Parameters:
// - path - The file path to read from (string).
//
// Returns:
// - The file data (Uint8Array).
// - Catches any thrown errors (of type Error) and returns it as an error.
func (rsCB *RemoteStoreCallbacks) Read(path string) ([]byte, error) {
fn := func() js.Value { return rsCB.read(path) }
v, err := utils.RunAndCatch(fn)
if err != nil {
return nil, err
}
return utils.CopyBytesToGo(v), err
}
// Write writes to the file path the provided data. An error is returned if it
// fails to write to file.
//
// Parameters:
// - path - The file path to write to (string).
// - data - The file data to write (Uint8Array).
//
// Returns:
// - Catches any thrown errors (of type Error) and returns it as an error.
func (rsCB *RemoteStoreCallbacks) Write(path string, data []byte) error {
fn := func() js.Value { return rsCB.write(path, utils.CopyBytesToJS(data)) }
_, err := utils.RunAndCatch(fn)
return err
}
// GetLastModified returns when the file at the given file path was last
// modified. If the implementation that adheres to this interface does not
// support this, [Write] or [Read] should be implemented to either write a
// separate timestamp file or add a prefix.
//
// Parameters:
// - path - The file path (string).
//
// Returns:
// - JSON of [bindings.RemoteStoreReport] (Uint8Array).
// - Catches any thrown errors (of type Error) and returns it as an error.
func (rsCB *RemoteStoreCallbacks) GetLastModified(path string) ([]byte, error) {
fn := func() js.Value { return rsCB.getLastModified(path) }
v, err := utils.RunAndCatch(fn)
if err != nil {
return nil, err
}
return utils.CopyBytesToGo(v), err
}
// GetLastWrite retrieves the most recent successful write operation that was
// received by [RemoteStoreFileSystem].
//
// Returns:
// - JSON of [bindings.RemoteStoreReport] (Uint8Array).
// - Catches any thrown errors (of type Error) and returns it as an error.
func (rsCB *RemoteStoreCallbacks) GetLastWrite() ([]byte, error) {
fn := func() js.Value { return rsCB.getLastWrite() }
v, err := utils.RunAndCatch(fn)
if err != nil {
return nil, err
}
return utils.CopyBytesToGo(v), err
}
// RemoteKVCallbacks wraps Javascript callbacks to adhere to the
// [bindings.RemoteKVCallbacks] interface.
type RemoteKVCallbacks struct {
keyUpdated func(args ...any) js.Value
remoteStoreResult func(args ...any) js.Value
}
// newRemoteKVCallbacks maps the functions of the Javascript object matching
// [bindings.RemoteKVCallbacks] to a RemoteKVCallbacks.
func newRemoteKVCallbacks(arg js.Value) *RemoteKVCallbacks {
return &RemoteKVCallbacks{
keyUpdated: utils.WrapCB(arg, "KeyUpdated"),
remoteStoreResult: utils.WrapCB(arg, "RemoteStoreResult"),
}
}
// KeyUpdated is the callback to be called any time a key is updated by another
// device tracked by the [RemoteKV] store.
//
// Parameters:
// - key - (string).
// - oldVal - (Uint8Array).
// - newVal - (Uint8Array).
// - updated - (Boolean)
func (rkvCB *RemoteKVCallbacks) KeyUpdated(
key string, oldVal, newVal []byte, updated bool) {
rkvCB.keyUpdated(
key, utils.CopyBytesToJS(oldVal), utils.CopyBytesToJS(newVal), updated)
}
// RemoteStoreResult is called to report network save results after the key has
// been updated locally.
//
// NOTE: Errors originate from the authentication and writing code in regard to
// remote which is handled by the user of this API. As a result, this callback
// provides no information in simple implementations.
//
// Parameters:
// - remoteStoreReport - JSON of [bindings.RemoteStoreReport] (Uint8Array).
func (rkvCB *RemoteKVCallbacks) RemoteStoreResult(remoteStoreReport []byte) {
rkvCB.remoteStoreResult(utils.CopyBytesToJS(remoteStoreReport))
}
////////////////////////////////////////////////////////////////////////////////
// Copyright © 2022 xx foundation //
// //
// Use of this source code is governed by a license that can be found in the //
// LICENSE file. //
////////////////////////////////////////////////////////////////////////////////
//go:build js && wasm
package wasm
import (
"reflect"
"testing"
"gitlab.com/elixxir/client/v4/bindings"
)
// Tests that the map representing RemoteStoreFileSystem returned by
// newRemoteStoreFileSystemJS contains all of the methods on
// RemoteStoreFileSystem.
func Test_newRemoteStoreFileSystemJS(t *testing.T) {
rsfType := reflect.TypeOf(&RemoteStoreFileSystem{})
rsf := newRemoteStoreFileSystemJS(&bindings.RemoteStoreFileSystem{})
if len(rsf) != rsfType.NumMethod() {
t.Errorf("RemoteStoreFileSystem JS object does not have all methods."+
"\nexpected: %d\nreceived: %d", rsfType.NumMethod(), len(rsf))
}
for i := 0; i < rsfType.NumMethod(); i++ {
method := rsfType.Method(i)
if _, exists := rsf[method.Name]; !exists {
t.Errorf("Method %s does not exist.", method.Name)
}
}
}
// Tests that RemoteStoreFileSystem has all the methods that
// [bindings.RemoteStoreFileSystem] has.
func Test_RemoteStoreFileSystemMethods(t *testing.T) {
rsfType := reflect.TypeOf(&RemoteStoreFileSystem{})
binRsfType := reflect.TypeOf(&bindings.RemoteStoreFileSystem{})
if binRsfType.NumMethod() != rsfType.NumMethod() {
t.Errorf("WASM RemoteStoreFileSystem object does not have all methods "+
"from bindings.\nexpected: %d\nreceived: %d",
binRsfType.NumMethod(), rsfType.NumMethod())
}
for i := 0; i < binRsfType.NumMethod(); i++ {
method := binRsfType.Method(i)
if _, exists := rsfType.MethodByName(method.Name); !exists {
t.Errorf("Method %s does not exist.", method.Name)
}
}
}
// Tests that the map representing RemoteKV returned by newRemoteKvJS contains
// all of the methods on RemoteKV.
func Test_newRemoteKvJS(t *testing.T) {
rkvType := reflect.TypeOf(&RemoteKV{})
rkv := newRemoteKvJS(&bindings.RemoteKV{})
if len(rkv) != rkvType.NumMethod() {
t.Errorf("RemoteKV JS object does not have all methods."+
"\nexpected: %d\nreceived: %d", rkvType.NumMethod(), len(rkv))
}
for i := 0; i < rkvType.NumMethod(); i++ {
method := rkvType.Method(i)
if _, exists := rkv[method.Name]; !exists {
t.Errorf("Method %s does not exist.", method.Name)
}
}
}
// Tests that RemoteKV has all the methods that [bindings.RemoteKV] has.
func Test_RemoteKVMethods(t *testing.T) {
rkvType := reflect.TypeOf(&RemoteKV{})
binRkvType := reflect.TypeOf(&bindings.RemoteKV{})
if binRkvType.NumMethod() != rkvType.NumMethod() {
t.Errorf("WASM RemoteKV object does not have all methods from "+
"bindings.\nexpected: %d\nreceived: %d",
binRkvType.NumMethod(), rkvType.NumMethod())
}
for i := 0; i < binRkvType.NumMethod(); i++ {
method := binRkvType.Method(i)
if _, exists := rkvType.MethodByName(method.Name); !exists {
t.Errorf("Method %s does not exist.", method.Name)
}
}
}