From ed7376adae205a5ed553a458a92458a3b3b7333e Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Tue, 6 May 2025 12:31:25 +0200 Subject: [PATCH 01/10] Added integration test based on Arduino Zero --- test/RpcClientTest/RpcClientTest.ino | 71 ++++++++++ test/RpcClientTest/RpcClient_test.go | 198 +++++++++++++++++++++++++++ test/RpcClientTest/go.mod | 19 +++ test/RpcClientTest/go.sum | 22 +++ 4 files changed, 310 insertions(+) create mode 100644 test/RpcClientTest/RpcClientTest.ino create mode 100644 test/RpcClientTest/RpcClient_test.go create mode 100644 test/RpcClientTest/go.mod create mode 100644 test/RpcClientTest/go.sum diff --git a/test/RpcClientTest/RpcClientTest.ino b/test/RpcClientTest/RpcClientTest.ino new file mode 100644 index 0000000..2195e37 --- /dev/null +++ b/test/RpcClientTest/RpcClientTest.ino @@ -0,0 +1,71 @@ +#include + +#ifdef ARDUINO_SAMD_ZERO +#define MSGPACKRPC Serial // MsgPack RPC runs on the hardware serial port (that do not disconnects on reset/upload) +#define DEBUG SerialUSB // Debug and upload port is the native USB +#else +#error "Unsupported board" +#endif + +SerialTransport transport(&MSGPACKRPC); +RPCClient rpc(transport); + +void setup() { + MSGPACKRPC.begin(115200); + DEBUG.begin(115200); + + while (!DEBUG) { /* WAIT for serial port to connect */ } + + testSuccessfulCallFl64(); + testWrongCall(); + testSuccessfulCallBool(); + // testWrongCall(); +} + +void testSuccessfulCallFl64() { + float result; + DEBUG.println("mult(2.0, 3.0)"); + bool ok = rpc.call("mult", result, 2.0, 3.0); + DEBUG.print("-> "); + if (ok) { + DEBUG.println(result); + } else { + DEBUG.println("error"); + } +} + +void testSuccessfulCallBool() { + bool result; + DEBUG.println("or(true, false)"); + bool ok = rpc.call("or", result, true, false); + DEBUG.print("-> "); + if (ok) { + DEBUG.println(result ? "true" : "false"); + } else { + DEBUG.println("error"); + } + + DEBUG.println("or(false)"); + ok = rpc.call("or", result, false); + DEBUG.print("-> "); + if (ok) { + DEBUG.println(result ? "true" : "false"); + } else { + DEBUG.println("error"); + } +} + +void testWrongCall() { + float result; + DEBUG.println("mult(2.0)"); + bool ok = rpc.call("mult", result, 2.0); + DEBUG.print("-> "); + if (ok) { + DEBUG.println(result); + } else { + DEBUG.println("error"); + } +} + +void loop() { +} diff --git a/test/RpcClientTest/RpcClient_test.go b/test/RpcClientTest/RpcClient_test.go new file mode 100644 index 0000000..e572d5e --- /dev/null +++ b/test/RpcClientTest/RpcClient_test.go @@ -0,0 +1,198 @@ +package RpcClientZeroTest + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "io" + "os" + "testing" + "time" + + "github.com/arduino/go-paths-helper" + "github.com/stretchr/testify/require" + "github.com/vmihailenco/msgpack/v5" + "go.bug.st/serial" +) + +func TestBasicComm(t *testing.T) { + // Get the upload port to upload the sketch + fqbn, _, uploadPort := getFQBNAndPorts(t) + { + // Upload the sketch + cli, err := paths.NewProcess(nil, "arduino-cli", "compile", "--fqbn", fqbn, "--library", "../..", "-u", "-p", uploadPort) + require.NoError(t, err) + cli.RedirectStderrTo(os.Stderr) + cli.RedirectStdoutTo(os.Stdout) + require.NoError(t, cli.Run()) + } + + // Get the rpc and debug ports + fqbn2, rpcPort, debugPort := getFQBNAndPorts(t) + require.Equal(t, fqbn, fqbn2, "FQBN mismatch between upload and run ports: %s != %s", fqbn, fqbn2) + + // Connect to the RPC serial port + _rpcSer, err := serial.Open(rpcPort, &serial.Mode{BaudRate: 115200}) + rpcSer := &DebugStream{upstream: _rpcSer, portname: rpcPort} + require.NoError(t, err) + t.Cleanup(func() { rpcSer.Close() }) + in := msgpack.NewDecoder(rpcSer) + out := msgpack.NewEncoder(rpcSer) + out.UseCompactInts(true) + + // Connect to the Debug serial port + debugSer, err := serial.Open(debugPort, &serial.Mode{BaudRate: 115200}) + require.NoError(t, err) + t.Cleanup(func() { debugSer.Close() }) + expectDebug := func(exp string) { + buff := make([]byte, len(exp)) + read := 0 + for read < len(exp) { + n, err := debugSer.Read(buff[read:]) + read += n + require.NoError(t, err) + } + require.Equal(t, exp, string(buff)) + } + + // Timeout fallback: close the connection after 10 seconds, if the test do not go through + go func() { + time.Sleep(10 * time.Second) + rpcSer.Close() + debugSer.Close() + }() + + // RPC: Receive an RPC call to the "mult" method with 2 arguments + // and send back the result + t.Run("RPCClientCallFloatArgs", func(t *testing.T) { + arr, err := in.DecodeSlice() + require.NoError(t, err) + require.Equal(t, []any{int8(0), int8(1), "mult", []any{2.0, 3.0}}, arr) + err = out.Encode([]any{1, 1, nil, 6.0}) + require.NoError(t, err) + expectDebug("mult(2.0, 3.0)\r\n") + expectDebug("-> 6.00\r\n") + }) + + // RPC: Receive an RPC call to the "mult" method with 1 argument (wrong number of arguments) + // and send back an error with [int, string] format + t.Run("RPCClientCallFloatArgsError", func(t *testing.T) { + arr, err := in.DecodeSlice() + require.NoError(t, err) + require.Equal(t, []any{int8(0), int8(2), "mult", []any{2.0}}, arr) + err = out.Encode([]any{1, 2, []any{1, "missing parameter"}, nil}) + require.NoError(t, err) + expectDebug("mult(2.0)\r\n") + expectDebug("-> error\r\n") + }) + + // RPC: Receive an RPC call to the "or" method with 1 or 2 arguments + // and send back the result + t.Run("RPCClientCallBoolArgs", func(t *testing.T) { + arr, err := in.DecodeSlice() + require.NoError(t, err) + require.Equal(t, []any{int8(0), int8(3), "or", []any{true, false}}, arr) + err = out.Encode([]any{1, 3, nil, true}) + require.NoError(t, err) + expectDebug("or(true, false)\r\n") + expectDebug("-> true\r\n") + + arr, err = in.DecodeSlice() + require.NoError(t, err) + require.Equal(t, []any{int8(0), int8(4), "or", []any{false}}, arr) + err = out.Encode([]any{1, 4, nil, false}) + require.NoError(t, err) + expectDebug("or(false)\r\n") + expectDebug("-> false\r\n") + }) + + // RPC: Receive an RPC call to the "mult" method with 1 argument (wrong number of arguments) + // and send back a custom error without [int, string] format + // t.Run("RPCClientCallFloatArgsErrorCustom", func(t *testing.T) { + // arr, err := in.DecodeSlice() + // require.NoError(t, err) + // require.Equal(t, []any{int8(0), int8(3), "mult", []any{2.0}}, arr) + // err = out.Encode([]any{1, 3, "missing parameter", nil}) + // require.NoError(t, err) + // expectDebug("mult(2.0)\r\n") + // expectDebug("-> error\r\n") + // }) +} + +func getFQBNAndPorts(t *testing.T) (fqbn string, rpcPort string, uploadPort string) { + cli, err := paths.NewProcess(nil, "arduino-cli", "board", "list", "--json") + require.NoError(t, err) + out, _, err := cli.RunAndCaptureOutput(t.Context()) + require.NoError(t, err) + var cliResult struct { + DetectedPorts []struct { + MatchingBoards []struct { + Fqbn string `json:"fqbn"` + } `json:"matching_boards"` + Port struct { + Address string `json:"address"` + } `json:"port"` + } `json:"detected_ports"` + } + require.NoError(t, json.Unmarshal(out, &cliResult)) + checkFQBN := func(boardFQBN string) { + if fqbn != boardFQBN { + fqbn = boardFQBN + uploadPort = "" + rpcPort = "" + } + } + for _, port := range cliResult.DetectedPorts { + for _, board := range port.MatchingBoards { + if board.Fqbn == "arduino:samd:arduino_zero_edbg" { + checkFQBN("arduino:samd:arduino_zero_native") + rpcPort = port.Port.Address + } + if board.Fqbn == "arduino:samd:arduino_zero_native" { + checkFQBN(board.Fqbn) + uploadPort = port.Port.Address + } + } + } + require.NotEmpty(t, uploadPort, "Upload port not found") + require.NotEmpty(t, rpcPort, "Debug port not found") + return fqbn, rpcPort, uploadPort +} + +// DebugStream is a wrapper around io.ReadWriteCloser that logs the data +// read and written to the stream in hex format. +// It is used to debug the communication with the Arduino board. +type DebugStream struct { + upstream io.ReadWriteCloser + portname string +} + +func (d *DebugStream) Read(p []byte) (n int, err error) { + n, err = d.upstream.Read(p) + if err != nil { + fmt.Printf("%s READ ERROR: %v\n", d.portname, err) + } else { + fmt.Printf("%s READ << %s\n", d.portname, hex.EncodeToString(p[:n])) + } + return n, err +} + +func (d *DebugStream) Write(p []byte) (n int, err error) { + n, err = d.upstream.Write(p) + if err != nil { + fmt.Printf("%s WRITE ERROR: %v\n", d.portname, err) + } else { + fmt.Printf("%s WRITE >> %s\n", d.portname, hex.EncodeToString(p[:n])) + } + return n, err +} + +func (d *DebugStream) Close() error { + err := d.upstream.Close() + fmt.Printf("%s CLOSE", d.portname) + if err != nil { + fmt.Printf(" (ERROR: %v)", err) + } + fmt.Println() + return err +} diff --git a/test/RpcClientTest/go.mod b/test/RpcClientTest/go.mod new file mode 100644 index 0000000..1214016 --- /dev/null +++ b/test/RpcClientTest/go.mod @@ -0,0 +1,19 @@ +module RpcClientZeroTest + +go 1.24.2 + +require ( + github.com/arduino/go-paths-helper v1.13.1 + github.com/stretchr/testify v1.8.4 + github.com/vmihailenco/msgpack/v5 v5.4.1 + go.bug.st/serial v1.6.4 +) + +require ( + github.com/creack/goselect v0.1.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/sys v0.32.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/test/RpcClientTest/go.sum b/test/RpcClientTest/go.sum new file mode 100644 index 0000000..9289a06 --- /dev/null +++ b/test/RpcClientTest/go.sum @@ -0,0 +1,22 @@ +github.com/arduino/go-paths-helper v1.13.1 h1:M7SCdLB2VldxOdChnjZkxAZwWZdDtNY4IlHL9nxGQFo= +github.com/arduino/go-paths-helper v1.13.1/go.mod h1:dDodKn2ZX4iwuoBMapdDO+5d0oDLBeM4BS0xS4i40Ak= +github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0= +github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= +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/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +go.bug.st/serial v1.6.4 h1:7FmqNPgVp3pu2Jz5PoPtbZ9jJO5gnEnZIvnI1lzve8A= +go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 264b31cfacfc093bae84f3b74bd63cb29fcb6bd8 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Tue, 6 May 2025 12:39:32 +0200 Subject: [PATCH 02/10] Added readme --- test/RpcClientTest/README.md | 133 +++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 test/RpcClientTest/README.md diff --git a/test/RpcClientTest/README.md b/test/RpcClientTest/README.md new file mode 100644 index 0000000..9cd7732 --- /dev/null +++ b/test/RpcClientTest/README.md @@ -0,0 +1,133 @@ +## Integration test for the RPC library + +This is a test that runs on the Arduino Zero board. It could be easily extended to other boards with multiple serial ports. + +### Running the test + +Prerequisites: + +* An Arduino Zero board, connected with both the USB ports. +* `arduino-cli` +* The platform `arduino:samd` installed through the `arduino-cli`. +* The dependencies of the RPClite library installed through the `arduino-cli`. +* A working `go` programming language compiler. + +To run the test, from a terminal change directory to this folder, and run: + +`go test -v` + +it should compile and upload the test sketch, and perform the RPC call tests. + +The output should look similar to the following: + +``` +RpcLite/test/RpcClientTest$ go test -v +=== RUN TestBasicComm +Lo sketch usa 27028 byte (10%) dello spazio disponibile per i programmi. Il massimo è 262144 byte. +Le variabili globali usano 4040 byte (12%) di memoria dinamica, lasciando altri 28728 byte liberi per le variabili locali. Il massimo è 32768 byte. +Atmel SMART device 0x10010005 found +Device : ATSAMD21G18A +Chip ID : 10010005 +Version : v2.0 [Arduino:XYZ] Apr 11 2019 13:09:49 +Address : 8192 +Pages : 3968 +Page Size : 64 bytes +Total Size : 248KB +Planes : 1 +Lock Regions : 16 +Locked : none +Security : false +Boot Flash : true +BOD : true +BOR : true +Arduino : FAST_CHIP_ERASE +Arduino : FAST_MULTI_PAGE_WRITE +Arduino : CAN_CHECKSUM_MEMORY_BUFFER +Erase flash +done in 0.873 seconds + +Write 27028 bytes to flash (423 pages) +[==============================] 100% (423/423 pages) +done in 0.155 seconds + +Verify 27028 bytes of flash with checksum. +Verify successful +done in 0.026 seconds +CPU reset. +=== RUN TestBasicComm/RPCClientCallFloatArgs +/dev/ttyACM0 READ << 94 +/dev/ttyACM0 READ << 0001 +/dev/ttyACM0 READ << a4 +/dev/ttyACM0 READ << 6d75 +/dev/ttyACM0 READ << 6c +/dev/ttyACM0 READ << 74 +/dev/ttyACM0 READ << 92cb +/dev/ttyACM0 READ << 40 +/dev/ttyACM0 READ << 0000 +/dev/ttyACM0 READ << 00 +/dev/ttyACM0 READ << 0000 +/dev/ttyACM0 READ << 00 +/dev/ttyACM0 READ << 00cb +/dev/ttyACM0 READ << 40 +/dev/ttyACM0 READ << 08 +/dev/ttyACM0 READ << 0000 +/dev/ttyACM0 READ << 00 +/dev/ttyACM0 READ << 0000 +/dev/ttyACM0 READ << 00 +/dev/ttyACM0 WRITE >> 94 +/dev/ttyACM0 WRITE >> 01 +/dev/ttyACM0 WRITE >> 01 +/dev/ttyACM0 WRITE >> c0 +/dev/ttyACM0 WRITE >> cb4018000000000000 +=== RUN TestBasicComm/RPCClientCallFloatArgsError +/dev/ttyACM0 READ << 9400 +/dev/ttyACM0 READ << 02a46d +/dev/ttyACM0 READ << 75 +/dev/ttyACM0 READ << 6c74 +/dev/ttyACM0 READ << 91 +/dev/ttyACM0 READ << cb40 +/dev/ttyACM0 READ << 00 +/dev/ttyACM0 READ << 00 +/dev/ttyACM0 READ << 0000 +/dev/ttyACM0 READ << 00 +/dev/ttyACM0 READ << 0000 +/dev/ttyACM0 WRITE >> 94 +/dev/ttyACM0 WRITE >> 01 +/dev/ttyACM0 WRITE >> 02 +/dev/ttyACM0 WRITE >> 92 +/dev/ttyACM0 WRITE >> 01 +/dev/ttyACM0 WRITE >> b1 +/dev/ttyACM0 WRITE >> 6d697373696e6720706172616d65746572 +/dev/ttyACM0 WRITE >> c0 +=== RUN TestBasicComm/RPCClientCallBoolArgs +/dev/ttyACM0 READ << 9400 +/dev/ttyACM0 READ << 03 +/dev/ttyACM0 READ << a26f +/dev/ttyACM0 READ << 72 +/dev/ttyACM0 READ << 92c3 +/dev/ttyACM0 READ << c2 +/dev/ttyACM0 WRITE >> 94 +/dev/ttyACM0 WRITE >> 01 +/dev/ttyACM0 WRITE >> 03 +/dev/ttyACM0 WRITE >> c0 +/dev/ttyACM0 WRITE >> c3 +/dev/ttyACM0 READ << 9400 +/dev/ttyACM0 READ << 04 +/dev/ttyACM0 READ << a26f +/dev/ttyACM0 READ << 72 +/dev/ttyACM0 READ << 91 +/dev/ttyACM0 READ << c2 +/dev/ttyACM0 WRITE >> 94 +/dev/ttyACM0 WRITE >> 01 +/dev/ttyACM0 WRITE >> 04 +/dev/ttyACM0 WRITE >> c0 +/dev/ttyACM0 WRITE >> c2 +/dev/ttyACM0 CLOSE +--- PASS: TestBasicComm (10.21s) + --- PASS: TestBasicComm/RPCClientCallFloatArgs (0.03s) + --- PASS: TestBasicComm/RPCClientCallFloatArgsError (0.03s) + --- PASS: TestBasicComm/RPCClientCallBoolArgs (0.01s) +PASS +ok RpcClientZeroTest 10.216s +RpcLite/test/RpcClientTest$ +``` \ No newline at end of file From 20026ba37a440e0e4b44192cc453c4809d308949 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Tue, 6 May 2025 16:12:25 +0200 Subject: [PATCH 03/10] Added support for Giga+ST/Link board combination --- test/RpcClientTest/RpcClientTest.ino | 3 +++ test/RpcClientTest/RpcClient_test.go | 17 ++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/test/RpcClientTest/RpcClientTest.ino b/test/RpcClientTest/RpcClientTest.ino index 2195e37..672ece5 100644 --- a/test/RpcClientTest/RpcClientTest.ino +++ b/test/RpcClientTest/RpcClientTest.ino @@ -3,6 +3,9 @@ #ifdef ARDUINO_SAMD_ZERO #define MSGPACKRPC Serial // MsgPack RPC runs on the hardware serial port (that do not disconnects on reset/upload) #define DEBUG SerialUSB // Debug and upload port is the native USB +#elif ARDUINO_GIGA +#define MSGPACKRPC Serial1 // MsgPack RPC runs on Serial1 +#define DEBUG SerialUSB // Debug and upload port is Serial #else #error "Unsupported board" #endif diff --git a/test/RpcClientTest/RpcClient_test.go b/test/RpcClientTest/RpcClient_test.go index e572d5e..8cfe379 100644 --- a/test/RpcClientTest/RpcClient_test.go +++ b/test/RpcClientTest/RpcClient_test.go @@ -130,7 +130,11 @@ func getFQBNAndPorts(t *testing.T) (fqbn string, rpcPort string, uploadPort stri Fqbn string `json:"fqbn"` } `json:"matching_boards"` Port struct { - Address string `json:"address"` + Address string `json:"address"` + Properties struct { + Vid string `json:"vid"` + Pid string `json:"pid"` + } `json:"properties"` } `json:"port"` } `json:"detected_ports"` } @@ -144,6 +148,10 @@ func getFQBNAndPorts(t *testing.T) (fqbn string, rpcPort string, uploadPort stri } for _, port := range cliResult.DetectedPorts { for _, board := range port.MatchingBoards { + if board.Fqbn == "arduino:mbed_giga:giga" { + checkFQBN(board.Fqbn) + uploadPort = port.Port.Address + } if board.Fqbn == "arduino:samd:arduino_zero_edbg" { checkFQBN("arduino:samd:arduino_zero_native") rpcPort = port.Port.Address @@ -154,6 +162,13 @@ func getFQBNAndPorts(t *testing.T) (fqbn string, rpcPort string, uploadPort stri } } } + if rpcPort == "" { + for _, port := range cliResult.DetectedPorts { + if port.Port.Properties.Vid == "0x0483" && port.Port.Properties.Pid == "0x374B" { + rpcPort = port.Port.Address + } + } + } require.NotEmpty(t, uploadPort, "Upload port not found") require.NotEmpty(t, rpcPort, "Debug port not found") return fqbn, rpcPort, uploadPort From 49914e705bf334355834027ca7b092635c8cb6ee Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Wed, 7 May 2025 15:04:12 +0200 Subject: [PATCH 04/10] Moved tests inside 'extras' folder --- {test => extras/integration_test}/RpcClientTest/README.md | 0 .../integration_test}/RpcClientTest/RpcClientTest.ino | 0 .../integration_test}/RpcClientTest/RpcClient_test.go | 2 +- {test => extras/integration_test}/RpcClientTest/go.mod | 0 {test => extras/integration_test}/RpcClientTest/go.sum | 0 5 files changed, 1 insertion(+), 1 deletion(-) rename {test => extras/integration_test}/RpcClientTest/README.md (100%) rename {test => extras/integration_test}/RpcClientTest/RpcClientTest.ino (100%) rename {test => extras/integration_test}/RpcClientTest/RpcClient_test.go (99%) rename {test => extras/integration_test}/RpcClientTest/go.mod (100%) rename {test => extras/integration_test}/RpcClientTest/go.sum (100%) diff --git a/test/RpcClientTest/README.md b/extras/integration_test/RpcClientTest/README.md similarity index 100% rename from test/RpcClientTest/README.md rename to extras/integration_test/RpcClientTest/README.md diff --git a/test/RpcClientTest/RpcClientTest.ino b/extras/integration_test/RpcClientTest/RpcClientTest.ino similarity index 100% rename from test/RpcClientTest/RpcClientTest.ino rename to extras/integration_test/RpcClientTest/RpcClientTest.ino diff --git a/test/RpcClientTest/RpcClient_test.go b/extras/integration_test/RpcClientTest/RpcClient_test.go similarity index 99% rename from test/RpcClientTest/RpcClient_test.go rename to extras/integration_test/RpcClientTest/RpcClient_test.go index 8cfe379..34e697d 100644 --- a/test/RpcClientTest/RpcClient_test.go +++ b/extras/integration_test/RpcClientTest/RpcClient_test.go @@ -20,7 +20,7 @@ func TestBasicComm(t *testing.T) { fqbn, _, uploadPort := getFQBNAndPorts(t) { // Upload the sketch - cli, err := paths.NewProcess(nil, "arduino-cli", "compile", "--fqbn", fqbn, "--library", "../..", "-u", "-p", uploadPort) + cli, err := paths.NewProcess(nil, "arduino-cli", "compile", "--fqbn", fqbn, "--library", "../../..", "-u", "-p", uploadPort) require.NoError(t, err) cli.RedirectStderrTo(os.Stderr) cli.RedirectStdoutTo(os.Stdout) diff --git a/test/RpcClientTest/go.mod b/extras/integration_test/RpcClientTest/go.mod similarity index 100% rename from test/RpcClientTest/go.mod rename to extras/integration_test/RpcClientTest/go.mod diff --git a/test/RpcClientTest/go.sum b/extras/integration_test/RpcClientTest/go.sum similarity index 100% rename from test/RpcClientTest/go.sum rename to extras/integration_test/RpcClientTest/go.sum From fdb2ee84e81f4fe8038f4cd5959cc6fd1e61c909 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Wed, 7 May 2025 15:10:44 +0200 Subject: [PATCH 05/10] Added RP2040 support --- extras/integration_test/RpcClientTest/RpcClientTest.ino | 3 +++ extras/integration_test/RpcClientTest/RpcClient_test.go | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/extras/integration_test/RpcClientTest/RpcClientTest.ino b/extras/integration_test/RpcClientTest/RpcClientTest.ino index 672ece5..aa0c86c 100644 --- a/extras/integration_test/RpcClientTest/RpcClientTest.ino +++ b/extras/integration_test/RpcClientTest/RpcClientTest.ino @@ -6,6 +6,9 @@ #elif ARDUINO_GIGA #define MSGPACKRPC Serial1 // MsgPack RPC runs on Serial1 #define DEBUG SerialUSB // Debug and upload port is Serial +#elif ARDUINO_NANO_RP2040_CONNECT +#define MSGPACKRPC Serial1 // MsgPack RPC runs on Serial1 +#define DEBUG SerialUSB // Debug and upload port is Serial #else #error "Unsupported board" #endif diff --git a/extras/integration_test/RpcClientTest/RpcClient_test.go b/extras/integration_test/RpcClientTest/RpcClient_test.go index 34e697d..2d76ab2 100644 --- a/extras/integration_test/RpcClientTest/RpcClient_test.go +++ b/extras/integration_test/RpcClientTest/RpcClient_test.go @@ -160,6 +160,10 @@ func getFQBNAndPorts(t *testing.T) (fqbn string, rpcPort string, uploadPort stri checkFQBN(board.Fqbn) uploadPort = port.Port.Address } + if board.Fqbn == "arduino:mbed_nano:nanorp2040connect" { + checkFQBN(board.Fqbn) + uploadPort = port.Port.Address + } } } if rpcPort == "" { @@ -167,6 +171,9 @@ func getFQBNAndPorts(t *testing.T) (fqbn string, rpcPort string, uploadPort stri if port.Port.Properties.Vid == "0x0483" && port.Port.Properties.Pid == "0x374B" { rpcPort = port.Port.Address } + if port.Port.Properties.Vid == "0x1A86" && port.Port.Properties.Pid == "0x55D4" { + rpcPort = port.Port.Address + } } } require.NotEmpty(t, uploadPort, "Upload port not found") From 74d4e2aa62f7942184b0ff6b33777279fd27da44 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Thu, 8 May 2025 14:43:14 +0200 Subject: [PATCH 06/10] Added test case --- .../RpcClientTest/RpcClientTest.ino | 6 ++++++ .../RpcClientTest/RpcClient_test.go | 18 +++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/extras/integration_test/RpcClientTest/RpcClientTest.ino b/extras/integration_test/RpcClientTest/RpcClientTest.ino index aa0c86c..7785909 100644 --- a/extras/integration_test/RpcClientTest/RpcClientTest.ino +++ b/extras/integration_test/RpcClientTest/RpcClientTest.ino @@ -22,9 +22,15 @@ void setup() { while (!DEBUG) { /* WAIT for serial port to connect */ } + // 1 testSuccessfulCallFl64(); + // 2 testWrongCall(); + // 3, 4 testSuccessfulCallBool(); + // 5 + testWrongCall(); + // testWrongCall(); } diff --git a/extras/integration_test/RpcClientTest/RpcClient_test.go b/extras/integration_test/RpcClientTest/RpcClient_test.go index 2d76ab2..07e5c72 100644 --- a/extras/integration_test/RpcClientTest/RpcClient_test.go +++ b/extras/integration_test/RpcClientTest/RpcClient_test.go @@ -62,7 +62,7 @@ func TestBasicComm(t *testing.T) { debugSer.Close() }() - // RPC: Receive an RPC call to the "mult" method with 2 arguments + // 1: Receive an RPC call to the "mult" method with 2 arguments // and send back the result t.Run("RPCClientCallFloatArgs", func(t *testing.T) { arr, err := in.DecodeSlice() @@ -74,7 +74,7 @@ func TestBasicComm(t *testing.T) { expectDebug("-> 6.00\r\n") }) - // RPC: Receive an RPC call to the "mult" method with 1 argument (wrong number of arguments) + // 2: Receive an RPC call to the "mult" method with 1 argument (wrong number of arguments) // and send back an error with [int, string] format t.Run("RPCClientCallFloatArgsError", func(t *testing.T) { arr, err := in.DecodeSlice() @@ -86,7 +86,7 @@ func TestBasicComm(t *testing.T) { expectDebug("-> error\r\n") }) - // RPC: Receive an RPC call to the "or" method with 1 or 2 arguments + // 3, 4: Receive an RPC call to the "or" method with 1 or 2 arguments // and send back the result t.Run("RPCClientCallBoolArgs", func(t *testing.T) { arr, err := in.DecodeSlice() @@ -106,6 +106,18 @@ func TestBasicComm(t *testing.T) { expectDebug("-> false\r\n") }) + // 5: Receive an RPC call to the "mult" method with 1 argument (wrong number of arguments) + // and send back an error with [int, string] format with a long string + t.Run("RPCClientCallFloatArgsErrorWithLongString", func(t *testing.T) { + arr, err := in.DecodeSlice() + require.NoError(t, err) + require.Equal(t, []any{int8(0), int8(5), "mult", []any{2.0}}, arr) + err = out.Encode([]any{1, 5, []any{2, "method get_led_state not available"}, nil}) + require.NoError(t, err) + expectDebug("mult(2.0)\r\n") + expectDebug("-> error\r\n") + }) + // RPC: Receive an RPC call to the "mult" method with 1 argument (wrong number of arguments) // and send back a custom error without [int, string] format // t.Run("RPCClientCallFloatArgsErrorCustom", func(t *testing.T) { From b62ec047f05f57d98e024a6172410a41673e031b Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Tue, 13 May 2025 21:07:27 +0200 Subject: [PATCH 07/10] Moved integration tests one level up. Factored test functions. Added server tests. --- .../{RpcClientTest => }/README.md | 0 extras/integration_test/RPCClient_test.go | 105 ++++++++ extras/integration_test/RPCServer_test.go | 93 +++++++ .../RpcClientTest/RpcClient_test.go | 232 ------------------ .../TestRPCClient.ino} | 0 .../TestRPCClient/serial_ports.h | 14 ++ .../TestRPCServer/TestRPCServer.ino | 41 ++++ .../TestRPCServer/serial_ports.h | 14 ++ .../{RpcClientTest => }/go.mod | 2 +- .../{RpcClientTest => }/go.sum | 0 extras/integration_test/serial_ports.h | 13 + extras/integration_test/testsuite.go | 161 ++++++++++++ 12 files changed, 442 insertions(+), 233 deletions(-) rename extras/integration_test/{RpcClientTest => }/README.md (100%) create mode 100644 extras/integration_test/RPCClient_test.go create mode 100644 extras/integration_test/RPCServer_test.go delete mode 100644 extras/integration_test/RpcClientTest/RpcClient_test.go rename extras/integration_test/{RpcClientTest/RpcClientTest.ino => TestRPCClient/TestRPCClient.ino} (100%) create mode 100644 extras/integration_test/TestRPCClient/serial_ports.h create mode 100644 extras/integration_test/TestRPCServer/TestRPCServer.ino create mode 100644 extras/integration_test/TestRPCServer/serial_ports.h rename extras/integration_test/{RpcClientTest => }/go.mod (93%) rename extras/integration_test/{RpcClientTest => }/go.sum (100%) create mode 100644 extras/integration_test/serial_ports.h create mode 100644 extras/integration_test/testsuite.go diff --git a/extras/integration_test/RpcClientTest/README.md b/extras/integration_test/README.md similarity index 100% rename from extras/integration_test/RpcClientTest/README.md rename to extras/integration_test/README.md diff --git a/extras/integration_test/RPCClient_test.go b/extras/integration_test/RPCClient_test.go new file mode 100644 index 0000000..44b8a1c --- /dev/null +++ b/extras/integration_test/RPCClient_test.go @@ -0,0 +1,105 @@ +package testsuite + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/vmihailenco/msgpack/v5" + "go.bug.st/serial" +) + +func TestRPCClient(t *testing.T) { + // Get the upload port to upload the sketch + rpcPort, debugPort := UploadSketchAndGetRPCAndDebugPorts(t) + + // Connect to the RPC serial port + _rpcSer, err := serial.Open(rpcPort, &serial.Mode{BaudRate: 115200}) + rpcSer := &DebugStream{Upstream: _rpcSer, Portname: rpcPort} + require.NoError(t, err) + t.Cleanup(func() { rpcSer.Close() }) + in := msgpack.NewDecoder(rpcSer) + out := msgpack.NewEncoder(rpcSer) + out.UseCompactInts(true) + + // Connect to the Debug serial port + debugSer, err := serial.Open(debugPort, &serial.Mode{BaudRate: 115200}) + require.NoError(t, err) + t.Cleanup(func() { debugSer.Close() }) + expectDebug := func(s string) { Expect(t, debugSer, s) } + + // Timeout fallback: close the connection after 10 seconds, if the test do not go through + go func() { + time.Sleep(10 * time.Second) + rpcSer.Close() + debugSer.Close() + }() + + // 1: Receive an RPC call to the "mult" method with 2 arguments + // and send back the result + t.Run("RPCClientCallFloatArgs", func(t *testing.T) { + arr, err := in.DecodeSlice() + require.NoError(t, err) + require.Equal(t, []any{int8(0), int8(1), "mult", []any{2.0, 3.0}}, arr) + err = out.Encode([]any{1, 1, nil, 6.0}) + require.NoError(t, err) + expectDebug("mult(2.0, 3.0)\r\n") + expectDebug("-> 6.00\r\n") + }) + + // 2: Receive an RPC call to the "mult" method with 1 argument (wrong number of arguments) + // and send back an error with [int, string] format + t.Run("RPCClientCallFloatArgsError", func(t *testing.T) { + arr, err := in.DecodeSlice() + require.NoError(t, err) + require.Equal(t, []any{int8(0), int8(2), "mult", []any{2.0}}, arr) + err = out.Encode([]any{1, 2, []any{1, "missing parameter"}, nil}) + require.NoError(t, err) + expectDebug("mult(2.0)\r\n") + expectDebug("-> error\r\n") + }) + + // 3, 4: Receive an RPC call to the "or" method with 1 or 2 arguments + // and send back the result + t.Run("RPCClientCallBoolArgs", func(t *testing.T) { + arr, err := in.DecodeSlice() + require.NoError(t, err) + require.Equal(t, []any{int8(0), int8(3), "or", []any{true, false}}, arr) + err = out.Encode([]any{1, 3, nil, true}) + require.NoError(t, err) + expectDebug("or(true, false)\r\n") + expectDebug("-> true\r\n") + + arr, err = in.DecodeSlice() + require.NoError(t, err) + require.Equal(t, []any{int8(0), int8(4), "or", []any{false}}, arr) + err = out.Encode([]any{1, 4, nil, false}) + require.NoError(t, err) + expectDebug("or(false)\r\n") + expectDebug("-> false\r\n") + }) + + // 5: Receive an RPC call to the "mult" method with 1 argument (wrong number of arguments) + // and send back an error with [int, string] format with a long string + t.Run("RPCClientCallFloatArgsErrorWithLongString", func(t *testing.T) { + arr, err := in.DecodeSlice() + require.NoError(t, err) + require.Equal(t, []any{int8(0), int8(5), "mult", []any{2.0}}, arr) + err = out.Encode([]any{1, 5, []any{2, "method get_led_state not available"}, nil}) + require.NoError(t, err) + expectDebug("mult(2.0)\r\n") + expectDebug("-> error\r\n") + }) + + // RPC: Receive an RPC call to the "mult" method with 1 argument (wrong number of arguments) + // and send back a custom error without [int, string] format + // t.Run("RPCClientCallFloatArgsErrorCustom", func(t *testing.T) { + // arr, err := in.DecodeSlice() + // require.NoError(t, err) + // require.Equal(t, []any{int8(0), int8(3), "mult", []any{2.0}}, arr) + // err = out.Encode([]any{1, 3, "missing parameter", nil}) + // require.NoError(t, err) + // expectDebug("mult(2.0)\r\n") + // expectDebug("-> error\r\n") + // }) +} diff --git a/extras/integration_test/RPCServer_test.go b/extras/integration_test/RPCServer_test.go new file mode 100644 index 0000000..5fbe9d8 --- /dev/null +++ b/extras/integration_test/RPCServer_test.go @@ -0,0 +1,93 @@ +package testsuite + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/vmihailenco/msgpack/v5" + "go.bug.st/serial" +) + +func TestRPCServer(t *testing.T) { + // Get the upload port to upload the sketch + rpcPort, debugPort := UploadSketchAndGetRPCAndDebugPorts(t) + + // Connect to the RPC serial port + _rpcSer, err := serial.Open(rpcPort, &serial.Mode{BaudRate: 115200}) + rpcSer := &DebugStream{Upstream: _rpcSer, Portname: rpcPort} + require.NoError(t, err) + t.Cleanup(func() { rpcSer.Close() }) + in := msgpack.NewDecoder(rpcSer) + out := msgpack.NewEncoder(rpcSer) + out.UseCompactInts(true) + + // Connect to the Debug serial port + debugSer, err := serial.Open(debugPort, &serial.Mode{BaudRate: 115200}) + require.NoError(t, err) + t.Cleanup(func() { debugSer.Close() }) + expectDebug := func(s string) { Expect(t, debugSer, s) } + + // Timeout fallback: close the connection after 10 seconds, if the test do not go through + go func() { + time.Sleep(10 * time.Second) + rpcSer.Close() + debugSer.Close() + }() + + // 1: Send an RPC call to the "add" method with 2 arguments + // and get back the result + t.Run("RPCServerCallIntArgs", func(t *testing.T) { + err = out.Encode([]any{0, 1, "add", []any{2, 3}}) + require.NoError(t, err) + expectDebug("add(2, 3)\r\n") + arr, err := in.DecodeSlice() + require.NoError(t, err) + require.Equal(t, []any{int8(1), int8(1), nil, int8(5)}, arr) + }) + + // 2, 3: Send the same RPC call with 1 and 3 arguments, and get back the error + t.Run("RPCServerCallWrongIntArgsCount", func(t *testing.T) { + err = out.Encode([]any{0, 2, "add", []any{2}}) + require.NoError(t, err) + arr, err := in.DecodeSlice() + require.NoError(t, err) + require.Equal(t, []any{ + int8(1), + int8(2), + []any{uint8(253), "Missing call parameters (WARNING: Default param resolution is not implemented)"}, + nil, + }, arr) + + err = out.Encode([]any{0, 3, "add", []any{2, 3, 4}}) + require.NoError(t, err) + arr, err = in.DecodeSlice() + require.NoError(t, err) + require.Equal(t, []any{ + int8(1), + int8(3), + []any{uint8(253), "Too many parameters"}, + nil, + }, arr) + }) + + // 4: Send an RPC call to the "greet" method + t.Run("RPCServerCallNoArgsReturnString", func(t *testing.T) { + err = out.Encode([]any{0, 4, "greet", []any{}}) + require.NoError(t, err) + expectDebug("greet()\r\n") + arr, err := in.DecodeSlice() + require.NoError(t, err) + require.Equal(t, []any{int8(1), int8(4), nil, "Hello World!"}, arr) + }) + + // 5: Send an RPC call to the "loopback" method with 1 string argument + t.Run("RPCServerCallStringArgsReturnString", func(t *testing.T) { + err = out.Encode([]any{0, 5, "loopback", []any{"Hello World!"}}) + require.NoError(t, err) + expectDebug("loopback(\"Hello World!\")\r\n") + arr, err := in.DecodeSlice() + require.NoError(t, err) + require.Equal(t, []any{int8(1), int8(5), nil, "Hello World!"}, arr) + }) +} diff --git a/extras/integration_test/RpcClientTest/RpcClient_test.go b/extras/integration_test/RpcClientTest/RpcClient_test.go deleted file mode 100644 index 07e5c72..0000000 --- a/extras/integration_test/RpcClientTest/RpcClient_test.go +++ /dev/null @@ -1,232 +0,0 @@ -package RpcClientZeroTest - -import ( - "encoding/hex" - "encoding/json" - "fmt" - "io" - "os" - "testing" - "time" - - "github.com/arduino/go-paths-helper" - "github.com/stretchr/testify/require" - "github.com/vmihailenco/msgpack/v5" - "go.bug.st/serial" -) - -func TestBasicComm(t *testing.T) { - // Get the upload port to upload the sketch - fqbn, _, uploadPort := getFQBNAndPorts(t) - { - // Upload the sketch - cli, err := paths.NewProcess(nil, "arduino-cli", "compile", "--fqbn", fqbn, "--library", "../../..", "-u", "-p", uploadPort) - require.NoError(t, err) - cli.RedirectStderrTo(os.Stderr) - cli.RedirectStdoutTo(os.Stdout) - require.NoError(t, cli.Run()) - } - - // Get the rpc and debug ports - fqbn2, rpcPort, debugPort := getFQBNAndPorts(t) - require.Equal(t, fqbn, fqbn2, "FQBN mismatch between upload and run ports: %s != %s", fqbn, fqbn2) - - // Connect to the RPC serial port - _rpcSer, err := serial.Open(rpcPort, &serial.Mode{BaudRate: 115200}) - rpcSer := &DebugStream{upstream: _rpcSer, portname: rpcPort} - require.NoError(t, err) - t.Cleanup(func() { rpcSer.Close() }) - in := msgpack.NewDecoder(rpcSer) - out := msgpack.NewEncoder(rpcSer) - out.UseCompactInts(true) - - // Connect to the Debug serial port - debugSer, err := serial.Open(debugPort, &serial.Mode{BaudRate: 115200}) - require.NoError(t, err) - t.Cleanup(func() { debugSer.Close() }) - expectDebug := func(exp string) { - buff := make([]byte, len(exp)) - read := 0 - for read < len(exp) { - n, err := debugSer.Read(buff[read:]) - read += n - require.NoError(t, err) - } - require.Equal(t, exp, string(buff)) - } - - // Timeout fallback: close the connection after 10 seconds, if the test do not go through - go func() { - time.Sleep(10 * time.Second) - rpcSer.Close() - debugSer.Close() - }() - - // 1: Receive an RPC call to the "mult" method with 2 arguments - // and send back the result - t.Run("RPCClientCallFloatArgs", func(t *testing.T) { - arr, err := in.DecodeSlice() - require.NoError(t, err) - require.Equal(t, []any{int8(0), int8(1), "mult", []any{2.0, 3.0}}, arr) - err = out.Encode([]any{1, 1, nil, 6.0}) - require.NoError(t, err) - expectDebug("mult(2.0, 3.0)\r\n") - expectDebug("-> 6.00\r\n") - }) - - // 2: Receive an RPC call to the "mult" method with 1 argument (wrong number of arguments) - // and send back an error with [int, string] format - t.Run("RPCClientCallFloatArgsError", func(t *testing.T) { - arr, err := in.DecodeSlice() - require.NoError(t, err) - require.Equal(t, []any{int8(0), int8(2), "mult", []any{2.0}}, arr) - err = out.Encode([]any{1, 2, []any{1, "missing parameter"}, nil}) - require.NoError(t, err) - expectDebug("mult(2.0)\r\n") - expectDebug("-> error\r\n") - }) - - // 3, 4: Receive an RPC call to the "or" method with 1 or 2 arguments - // and send back the result - t.Run("RPCClientCallBoolArgs", func(t *testing.T) { - arr, err := in.DecodeSlice() - require.NoError(t, err) - require.Equal(t, []any{int8(0), int8(3), "or", []any{true, false}}, arr) - err = out.Encode([]any{1, 3, nil, true}) - require.NoError(t, err) - expectDebug("or(true, false)\r\n") - expectDebug("-> true\r\n") - - arr, err = in.DecodeSlice() - require.NoError(t, err) - require.Equal(t, []any{int8(0), int8(4), "or", []any{false}}, arr) - err = out.Encode([]any{1, 4, nil, false}) - require.NoError(t, err) - expectDebug("or(false)\r\n") - expectDebug("-> false\r\n") - }) - - // 5: Receive an RPC call to the "mult" method with 1 argument (wrong number of arguments) - // and send back an error with [int, string] format with a long string - t.Run("RPCClientCallFloatArgsErrorWithLongString", func(t *testing.T) { - arr, err := in.DecodeSlice() - require.NoError(t, err) - require.Equal(t, []any{int8(0), int8(5), "mult", []any{2.0}}, arr) - err = out.Encode([]any{1, 5, []any{2, "method get_led_state not available"}, nil}) - require.NoError(t, err) - expectDebug("mult(2.0)\r\n") - expectDebug("-> error\r\n") - }) - - // RPC: Receive an RPC call to the "mult" method with 1 argument (wrong number of arguments) - // and send back a custom error without [int, string] format - // t.Run("RPCClientCallFloatArgsErrorCustom", func(t *testing.T) { - // arr, err := in.DecodeSlice() - // require.NoError(t, err) - // require.Equal(t, []any{int8(0), int8(3), "mult", []any{2.0}}, arr) - // err = out.Encode([]any{1, 3, "missing parameter", nil}) - // require.NoError(t, err) - // expectDebug("mult(2.0)\r\n") - // expectDebug("-> error\r\n") - // }) -} - -func getFQBNAndPorts(t *testing.T) (fqbn string, rpcPort string, uploadPort string) { - cli, err := paths.NewProcess(nil, "arduino-cli", "board", "list", "--json") - require.NoError(t, err) - out, _, err := cli.RunAndCaptureOutput(t.Context()) - require.NoError(t, err) - var cliResult struct { - DetectedPorts []struct { - MatchingBoards []struct { - Fqbn string `json:"fqbn"` - } `json:"matching_boards"` - Port struct { - Address string `json:"address"` - Properties struct { - Vid string `json:"vid"` - Pid string `json:"pid"` - } `json:"properties"` - } `json:"port"` - } `json:"detected_ports"` - } - require.NoError(t, json.Unmarshal(out, &cliResult)) - checkFQBN := func(boardFQBN string) { - if fqbn != boardFQBN { - fqbn = boardFQBN - uploadPort = "" - rpcPort = "" - } - } - for _, port := range cliResult.DetectedPorts { - for _, board := range port.MatchingBoards { - if board.Fqbn == "arduino:mbed_giga:giga" { - checkFQBN(board.Fqbn) - uploadPort = port.Port.Address - } - if board.Fqbn == "arduino:samd:arduino_zero_edbg" { - checkFQBN("arduino:samd:arduino_zero_native") - rpcPort = port.Port.Address - } - if board.Fqbn == "arduino:samd:arduino_zero_native" { - checkFQBN(board.Fqbn) - uploadPort = port.Port.Address - } - if board.Fqbn == "arduino:mbed_nano:nanorp2040connect" { - checkFQBN(board.Fqbn) - uploadPort = port.Port.Address - } - } - } - if rpcPort == "" { - for _, port := range cliResult.DetectedPorts { - if port.Port.Properties.Vid == "0x0483" && port.Port.Properties.Pid == "0x374B" { - rpcPort = port.Port.Address - } - if port.Port.Properties.Vid == "0x1A86" && port.Port.Properties.Pid == "0x55D4" { - rpcPort = port.Port.Address - } - } - } - require.NotEmpty(t, uploadPort, "Upload port not found") - require.NotEmpty(t, rpcPort, "Debug port not found") - return fqbn, rpcPort, uploadPort -} - -// DebugStream is a wrapper around io.ReadWriteCloser that logs the data -// read and written to the stream in hex format. -// It is used to debug the communication with the Arduino board. -type DebugStream struct { - upstream io.ReadWriteCloser - portname string -} - -func (d *DebugStream) Read(p []byte) (n int, err error) { - n, err = d.upstream.Read(p) - if err != nil { - fmt.Printf("%s READ ERROR: %v\n", d.portname, err) - } else { - fmt.Printf("%s READ << %s\n", d.portname, hex.EncodeToString(p[:n])) - } - return n, err -} - -func (d *DebugStream) Write(p []byte) (n int, err error) { - n, err = d.upstream.Write(p) - if err != nil { - fmt.Printf("%s WRITE ERROR: %v\n", d.portname, err) - } else { - fmt.Printf("%s WRITE >> %s\n", d.portname, hex.EncodeToString(p[:n])) - } - return n, err -} - -func (d *DebugStream) Close() error { - err := d.upstream.Close() - fmt.Printf("%s CLOSE", d.portname) - if err != nil { - fmt.Printf(" (ERROR: %v)", err) - } - fmt.Println() - return err -} diff --git a/extras/integration_test/RpcClientTest/RpcClientTest.ino b/extras/integration_test/TestRPCClient/TestRPCClient.ino similarity index 100% rename from extras/integration_test/RpcClientTest/RpcClientTest.ino rename to extras/integration_test/TestRPCClient/TestRPCClient.ino diff --git a/extras/integration_test/TestRPCClient/serial_ports.h b/extras/integration_test/TestRPCClient/serial_ports.h new file mode 100644 index 0000000..3c47da5 --- /dev/null +++ b/extras/integration_test/TestRPCClient/serial_ports.h @@ -0,0 +1,14 @@ +// This file is automatically generated from ../serial_ports.h. DO NOT EDIT. + +#ifdef ARDUINO_SAMD_ZERO +#define MSGPACKRPC Serial // MsgPack RPC runs on the hardware serial port (that do not disconnects on reset/upload) +#define DEBUG SerialUSB // Debug and upload port is the native USB +#elif ARDUINO_GIGA +#define MSGPACKRPC Serial1 // MsgPack RPC runs on Serial1 +#define DEBUG SerialUSB // Debug and upload port is Serial +#elif ARDUINO_NANO_RP2040_CONNECT +#define MSGPACKRPC Serial1 // MsgPack RPC runs on Serial1 +#define DEBUG SerialUSB // Debug and upload port is Serial +#else +#error "Unsupported board" +#endif diff --git a/extras/integration_test/TestRPCServer/TestRPCServer.ino b/extras/integration_test/TestRPCServer/TestRPCServer.ino new file mode 100644 index 0000000..8ba1342 --- /dev/null +++ b/extras/integration_test/TestRPCServer/TestRPCServer.ino @@ -0,0 +1,41 @@ +#include +#include "serial_ports.h" + +SerialTransport transport(&MSGPACKRPC); +RPCServer server(transport); + +int add(int a, int b) { + DEBUG.print("add("); + DEBUG.print(a); + DEBUG.print(", "); + DEBUG.print(b); + DEBUG.println(")"); + return a+b; +} + +MsgPack::str_t greet() { + DEBUG.println("greet()"); + return MsgPack::str_t ("Hello World!"); +} + +MsgPack::str_t loopback(MsgPack::str_t message){ + DEBUG.print("loopback(\""); + DEBUG.print(message); + DEBUG.println("\")"); + return message; +} + +void setup() { + MSGPACKRPC.begin(115200); + DEBUG.begin(115200); + transport.begin(); + server.bind("add", add); + server.bind("greet", greet); + server.bind("loopback", loopback); + + while (!DEBUG) { /* WAIT for serial port to connect */ } +} + +void loop() { + server.run(); +} diff --git a/extras/integration_test/TestRPCServer/serial_ports.h b/extras/integration_test/TestRPCServer/serial_ports.h new file mode 100644 index 0000000..3c47da5 --- /dev/null +++ b/extras/integration_test/TestRPCServer/serial_ports.h @@ -0,0 +1,14 @@ +// This file is automatically generated from ../serial_ports.h. DO NOT EDIT. + +#ifdef ARDUINO_SAMD_ZERO +#define MSGPACKRPC Serial // MsgPack RPC runs on the hardware serial port (that do not disconnects on reset/upload) +#define DEBUG SerialUSB // Debug and upload port is the native USB +#elif ARDUINO_GIGA +#define MSGPACKRPC Serial1 // MsgPack RPC runs on Serial1 +#define DEBUG SerialUSB // Debug and upload port is Serial +#elif ARDUINO_NANO_RP2040_CONNECT +#define MSGPACKRPC Serial1 // MsgPack RPC runs on Serial1 +#define DEBUG SerialUSB // Debug and upload port is Serial +#else +#error "Unsupported board" +#endif diff --git a/extras/integration_test/RpcClientTest/go.mod b/extras/integration_test/go.mod similarity index 93% rename from extras/integration_test/RpcClientTest/go.mod rename to extras/integration_test/go.mod index 1214016..aac91de 100644 --- a/extras/integration_test/RpcClientTest/go.mod +++ b/extras/integration_test/go.mod @@ -1,4 +1,4 @@ -module RpcClientZeroTest +module ArduinoRPCliteTestSuite go 1.24.2 diff --git a/extras/integration_test/RpcClientTest/go.sum b/extras/integration_test/go.sum similarity index 100% rename from extras/integration_test/RpcClientTest/go.sum rename to extras/integration_test/go.sum diff --git a/extras/integration_test/serial_ports.h b/extras/integration_test/serial_ports.h new file mode 100644 index 0000000..f0c738b --- /dev/null +++ b/extras/integration_test/serial_ports.h @@ -0,0 +1,13 @@ + +#ifdef ARDUINO_SAMD_ZERO +#define MSGPACKRPC Serial // MsgPack RPC runs on the hardware serial port (that do not disconnects on reset/upload) +#define DEBUG SerialUSB // Debug and upload port is the native USB +#elif ARDUINO_GIGA +#define MSGPACKRPC Serial1 // MsgPack RPC runs on Serial1 +#define DEBUG SerialUSB // Debug and upload port is Serial +#elif ARDUINO_NANO_RP2040_CONNECT +#define MSGPACKRPC Serial1 // MsgPack RPC runs on Serial1 +#define DEBUG SerialUSB // Debug and upload port is Serial +#else +#error "Unsupported board" +#endif diff --git a/extras/integration_test/testsuite.go b/extras/integration_test/testsuite.go new file mode 100644 index 0000000..d783640 --- /dev/null +++ b/extras/integration_test/testsuite.go @@ -0,0 +1,161 @@ +package testsuite + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "io" + "os" + "testing" + + "github.com/arduino/go-paths-helper" + "github.com/stretchr/testify/require" +) + +// UploadSketchAndGetRPCAndDebugPorts uploads the sketch to the board and +// returns the RPC and debug ports. The sketch is supposed to be +// located in the current directory. +func UploadSketchAndGetRPCAndDebugPorts(t *testing.T) (rpc string, debug string) { + sketchName := t.Name() + fqbn, _, uploadPort := getFQBNAndPorts(t) + { + // Copy serial_ports.h file to the sketch directory + serialPortHeader := []byte(fmt.Sprintln("// This file is automatically generated from ../serial_ports.h. DO NOT EDIT.")) + serialPortsH, err := paths.New("serial_ports.h").ReadFile() + require.NoError(t, err) + err = paths.New(sketchName, "serial_ports.h").WriteFile(append(serialPortHeader, serialPortsH...)) + require.NoError(t, err) + + // Upload the sketch + cli, err := paths.NewProcess(nil, "arduino-cli", "compile", "--fqbn", fqbn, "--library", "../..", "-u", "-p", uploadPort, sketchName) + require.NoError(t, err) + cli.RedirectStderrTo(os.Stderr) + cli.RedirectStdoutTo(os.Stdout) + require.NoError(t, cli.Run()) + } + + // Get the rpc and debug ports + fqbn2, rpcPort, debugPort := getFQBNAndPorts(t) + require.Equal(t, fqbn, fqbn2, "FQBN mismatch between upload and run ports: %s != %s", fqbn, fqbn2) + return rpcPort, debugPort +} + +// getFQBNAndPorts retrieves the FQBN of the board under test and the +// corresponding upload and RPC ports. Debugging messages will be output +// in the upload port, the RPC communication will be done on the RPC port. +// If the board do not have a second serial port, the RPC port will be +// assigned to a USB-2-Serial dongle/converter if found. +func getFQBNAndPorts(t *testing.T) (fqbn string, rpcPort string, uploadPort string) { + cli, err := paths.NewProcess(nil, "arduino-cli", "board", "list", "--json") + require.NoError(t, err) + out, _, err := cli.RunAndCaptureOutput(t.Context()) + require.NoError(t, err) + var cliResult struct { + DetectedPorts []struct { + MatchingBoards []struct { + Fqbn string `json:"fqbn"` + } `json:"matching_boards"` + Port struct { + Address string `json:"address"` + Properties struct { + Vid string `json:"vid"` + Pid string `json:"pid"` + } `json:"properties"` + } `json:"port"` + } `json:"detected_ports"` + } + require.NoError(t, json.Unmarshal(out, &cliResult)) + checkFQBN := func(boardFQBN string) { + if fqbn != boardFQBN { + fqbn = boardFQBN + uploadPort = "" + rpcPort = "" + } + } + for _, port := range cliResult.DetectedPorts { + for _, board := range port.MatchingBoards { + if board.Fqbn == "arduino:mbed_giga:giga" { + checkFQBN(board.Fqbn) + uploadPort = port.Port.Address + } + if board.Fqbn == "arduino:samd:arduino_zero_edbg" { + checkFQBN("arduino:samd:arduino_zero_native") + rpcPort = port.Port.Address + } + if board.Fqbn == "arduino:samd:arduino_zero_native" { + checkFQBN(board.Fqbn) + uploadPort = port.Port.Address + } + if board.Fqbn == "arduino:mbed_nano:nanorp2040connect" { + checkFQBN(board.Fqbn) + uploadPort = port.Port.Address + } + } + } + if rpcPort == "" { + for _, port := range cliResult.DetectedPorts { + if port.Port.Properties.Vid == "0x0483" && port.Port.Properties.Pid == "0x374B" { + rpcPort = port.Port.Address + } + if port.Port.Properties.Vid == "0x1A86" && port.Port.Properties.Pid == "0x55D4" { + rpcPort = port.Port.Address + } + } + } + require.NotEmpty(t, uploadPort, "Upload port not found") + require.NotEmpty(t, rpcPort, "Debug port not found") + return fqbn, rpcPort, uploadPort +} + +// Expect checks that the input stream returns the expected +// string. It reads the input stream until it has read the +// expected number of bytes. It is used to check the output +// of the Arduino board. +func Expect(t *testing.T, in io.Reader, expected string) { + buff := make([]byte, len(expected)) + read := 0 + for read < len(expected) { + n, err := in.Read(buff[read:]) + require.NoError(t, err) + read += n + } + require.Equal(t, expected, string(buff)) +} + +// DebugStream is a wrapper around io.ReadWriteCloser that logs the data +// read and written to the stream in hex format. +// It is used to debug the communication with the Arduino board. +type DebugStream struct { + Upstream io.ReadWriteCloser + Portname string +} + +func (d *DebugStream) Read(p []byte) (n int, err error) { + n, err = d.Upstream.Read(p) + if err != nil { + fmt.Printf("%s READ ERROR: %v\n", d.Portname, err) + } else { + fmt.Printf("%s READ << %s\n", d.Portname, hex.EncodeToString(p[:n])) + } + return n, err +} + +func (d *DebugStream) Write(p []byte) (n int, err error) { + n, err = d.Upstream.Write(p) + if err != nil { + fmt.Printf("%s WRITE ERROR: %v\n", d.Portname, err) + } else { + fmt.Printf("%s WRITE >> %s\n", d.Portname, hex.EncodeToString(p[:n])) + } + return n, err +} + +func (d *DebugStream) Close() error { + err := d.Upstream.Close() + fmt.Printf("%s CLOSE", d.Portname) + if err != nil { + fmt.Printf(" (ERROR: %v)", err) + } + fmt.Println() + return err +} From 452ff813fcf19f4a50cc3ea55e82914433ff8ecd Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Tue, 13 May 2025 22:00:19 +0200 Subject: [PATCH 08/10] Removing assert causing a build failure. --- src/dispatcher.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dispatcher.h b/src/dispatcher.h index 7527da3..7320db9 100644 --- a/src/dispatcher.h +++ b/src/dispatcher.h @@ -15,7 +15,7 @@ class RpcFunctionDispatcher { public: template void bind(MsgPack::str_t name, F&& f) { - assert(_count < N); + //assert(_count < N); static auto wrapper = wrap(std::forward(f)); _entries[_count++] = {name, &wrapper}; } From fe56aae8265980888057fe3bb44813460166837f Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Mon, 26 May 2025 18:52:11 +0200 Subject: [PATCH 09/10] Renumbered test messages in RPCClient integration tests --- extras/integration_test/RPCClient_test.go | 27 ++++++++++++++--------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/extras/integration_test/RPCClient_test.go b/extras/integration_test/RPCClient_test.go index 44b8a1c..8836470 100644 --- a/extras/integration_test/RPCClient_test.go +++ b/extras/integration_test/RPCClient_test.go @@ -35,16 +35,19 @@ func TestRPCClient(t *testing.T) { debugSer.Close() }() + msgID := 0 + // 1: Receive an RPC call to the "mult" method with 2 arguments // and send back the result t.Run("RPCClientCallFloatArgs", func(t *testing.T) { arr, err := in.DecodeSlice() require.NoError(t, err) - require.Equal(t, []any{int8(0), int8(1), "mult", []any{2.0, 3.0}}, arr) - err = out.Encode([]any{1, 1, nil, 6.0}) + require.Equal(t, []any{int8(0), int8(msgID), "mult", []any{2.0, 3.0}}, arr) + err = out.Encode([]any{1, msgID, nil, 6.0}) require.NoError(t, err) expectDebug("mult(2.0, 3.0)\r\n") expectDebug("-> 6.00\r\n") + msgID++ }) // 2: Receive an RPC call to the "mult" method with 1 argument (wrong number of arguments) @@ -52,11 +55,12 @@ func TestRPCClient(t *testing.T) { t.Run("RPCClientCallFloatArgsError", func(t *testing.T) { arr, err := in.DecodeSlice() require.NoError(t, err) - require.Equal(t, []any{int8(0), int8(2), "mult", []any{2.0}}, arr) - err = out.Encode([]any{1, 2, []any{1, "missing parameter"}, nil}) + require.Equal(t, []any{int8(0), int8(msgID), "mult", []any{2.0}}, arr) + err = out.Encode([]any{1, msgID, []any{1, "missing parameter"}, nil}) require.NoError(t, err) expectDebug("mult(2.0)\r\n") expectDebug("-> error\r\n") + msgID++ }) // 3, 4: Receive an RPC call to the "or" method with 1 or 2 arguments @@ -64,19 +68,21 @@ func TestRPCClient(t *testing.T) { t.Run("RPCClientCallBoolArgs", func(t *testing.T) { arr, err := in.DecodeSlice() require.NoError(t, err) - require.Equal(t, []any{int8(0), int8(3), "or", []any{true, false}}, arr) - err = out.Encode([]any{1, 3, nil, true}) + require.Equal(t, []any{int8(0), int8(msgID), "or", []any{true, false}}, arr) + err = out.Encode([]any{1, msgID, nil, true}) require.NoError(t, err) expectDebug("or(true, false)\r\n") expectDebug("-> true\r\n") + msgID++ arr, err = in.DecodeSlice() require.NoError(t, err) - require.Equal(t, []any{int8(0), int8(4), "or", []any{false}}, arr) - err = out.Encode([]any{1, 4, nil, false}) + require.Equal(t, []any{int8(0), int8(msgID), "or", []any{false}}, arr) + err = out.Encode([]any{1, msgID, nil, false}) require.NoError(t, err) expectDebug("or(false)\r\n") expectDebug("-> false\r\n") + msgID++ }) // 5: Receive an RPC call to the "mult" method with 1 argument (wrong number of arguments) @@ -84,11 +90,12 @@ func TestRPCClient(t *testing.T) { t.Run("RPCClientCallFloatArgsErrorWithLongString", func(t *testing.T) { arr, err := in.DecodeSlice() require.NoError(t, err) - require.Equal(t, []any{int8(0), int8(5), "mult", []any{2.0}}, arr) - err = out.Encode([]any{1, 5, []any{2, "method get_led_state not available"}, nil}) + require.Equal(t, []any{int8(0), int8(msgID), "mult", []any{2.0}}, arr) + err = out.Encode([]any{1, msgID, []any{2, "method get_led_state not available"}, nil}) require.NoError(t, err) expectDebug("mult(2.0)\r\n") expectDebug("-> error\r\n") + msgID++ }) // RPC: Receive an RPC call to the "mult" method with 1 argument (wrong number of arguments) From 19c757b1ab93671e3dba3408dd08b7b3a08d906a Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Mon, 26 May 2025 18:53:32 +0200 Subject: [PATCH 10/10] [draft] silence debugging messages --- src/client.h | 4 ++-- src/decoder.h | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/client.h b/src/client.h index 4d63e8d..17098ef 100644 --- a/src/client.h +++ b/src/client.h @@ -36,8 +36,8 @@ class RPCClient { #ifdef DEBUG if (error.code != NO_ERR){ - Serial.print("Server-side error message: "); - Serial.println(error.traceback); + //Serial.print("Server-side error message: "); + //Serial.println(error.traceback); } #endif diff --git a/src/decoder.h b/src/decoder.h index 2bc89a4..cf3677a 100644 --- a/src/decoder.h +++ b/src/decoder.h @@ -259,15 +259,15 @@ class RpcDecoder { #ifdef DEBUG void print_buffer(){ - Serial.print("buf size: "); - Serial.print(_bytes_stored); - Serial.print(" : "); + //Serial.print("buf size: "); + //Serial.print(_bytes_stored); + //Serial.print(" : "); for (size_t i = 0; i < _bytes_stored; i++) { - Serial.print(_raw_buffer[i], HEX); - Serial.print(" "); + //Serial.print(_raw_buffer[i], HEX); + //Serial.print(" "); } - Serial.println(); + //Serial.println(); } #endif