Skip to content

gRPC: if an Upload request is canceled, immediately terminate the upload tool process. #2726

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Added integration test
  • Loading branch information
cmaglie committed Oct 9, 2024
commit 8232687d7183d724e96242780d6170da10b48c8b
52 changes: 52 additions & 0 deletions internal/integrationtest/arduino-cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,42 @@ func (cli *ArduinoCLI) InstallMockedSerialMonitor(t *testing.T) {
}
}

// InstallMockedAvrdude will replace the already installed avrdude with a mocked one.
func (cli *ArduinoCLI) InstallMockedAvrdude(t *testing.T) {
fmt.Println(color.BlueString("<<< Install mocked avrdude"))

// Build mocked serial-discovery
mockDir := FindRepositoryRootPath(t).Join("internal", "mock_avrdude")
gobuild, err := paths.NewProcess(nil, "go", "build")
require.NoError(t, err)
gobuild.SetDirFromPath(mockDir)
require.NoError(t, gobuild.Run(), "Building mocked avrdude")
ext := ""
if runtime.GOOS == "windows" {
ext = ".exe"
}
mockBin := mockDir.Join("mock_avrdude" + ext)
require.True(t, mockBin.Exist())
fmt.Println(color.HiBlackString(" Build of mocked avrdude succeeded."))

// Install it replacing the current avrdudes
dataDir := cli.DataDir()
require.NotNil(t, dataDir, "data dir missing")

avrdudes, err := dataDir.Join("packages", "arduino", "tools", "avrdude").ReadDirRecursiveFiltered(
nil, paths.AndFilter(
paths.FilterNames("avrdude"+ext),
paths.FilterOutDirectories(),
),
)
require.NoError(t, err, "scanning data dir for avrdude(s)")
require.NotEmpty(t, avrdudes, "no avrdude(s) found in data dir")
for _, avrdude := range avrdudes {
require.NoError(t, mockBin.CopyTo(avrdude), "installing mocked avrdude to %s", avrdude)
fmt.Println(color.HiBlackString(" Mocked avrdude installed in " + avrdude.String()))
}
}

// RunWithCustomEnv executes the given arduino-cli command with the given custom env and returns the output.
func (cli *ArduinoCLI) RunWithCustomEnv(env map[string]string, args ...string) ([]byte, []byte, error) {
var stdoutBuf, stderrBuf bytes.Buffer
Expand Down Expand Up @@ -642,3 +678,19 @@ func (inst *ArduinoCLIInstance) Monitor(ctx context.Context, port *commands.Port
})
return monitorClient, err
}

// Upload calls the "Upload" gRPC method.
func (inst *ArduinoCLIInstance) Upload(ctx context.Context, fqbn, sketchPath, port, protocol string) (commands.ArduinoCoreService_UploadClient, error) {
uploadCl, err := inst.cli.daemonClient.Upload(ctx, &commands.UploadRequest{
Instance: inst.instance,
Fqbn: fqbn,
SketchPath: sketchPath,
Verbose: true,
Port: &commands.Port{
Address: port,
Protocol: protocol,
},
})
logCallf(">>> Upload(%v %v port/protocol=%s/%s)\n", fqbn, sketchPath, port, protocol)
return uploadCl, err
}
120 changes: 120 additions & 0 deletions internal/integrationtest/daemon/upload_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// This file is part of arduino-cli.
//
// Copyright 2024 ARDUINO SA (http://www.arduino.cc/)
//
// This software is released under the GNU General Public License version 3,
// which covers the main part of arduino-cli.
// The terms of this license can be found at:
// https://www.gnu.org/licenses/gpl-3.0.en.html
//
// You can be released from the requirements of the above licenses by purchasing
// a commercial license. Buying such a license is mandatory if you want to
// modify or otherwise use the software for commercial activities involving the
// Arduino software without disclosing the source code of your own applications.
// To purchase a commercial license, send an email to license@arduino.cc.

package daemon_test

import (
"context"
"errors"
"fmt"
"io"
"os"
"strings"
"testing"
"time"

"github.com/arduino/arduino-cli/internal/integrationtest"
"github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/arduino/go-paths-helper"
"github.com/stretchr/testify/require"
)

func TestUploadCancelation(t *testing.T) {
env, cli := integrationtest.CreateEnvForDaemon(t)
defer env.CleanUp()

grpcInst := cli.Create()
require.NoError(t, grpcInst.Init("", "", func(ir *commands.InitResponse) {
fmt.Printf("INIT> %v\n", ir.GetMessage())
}))

plInst, err := grpcInst.PlatformInstall(context.Background(), "arduino", "avr", "1.8.6", true)
require.NoError(t, err)
for {
msg, err := plInst.Recv()
if errors.Is(err, io.EOF) {
break
}
require.NoError(t, err)
fmt.Printf("INSTALL> %v\n", msg)
}

// Mock avrdude
cli.InstallMockedAvrdude(t)

// Re-init instance to update changes
require.NoError(t, grpcInst.Init("", "", func(ir *commands.InitResponse) {
fmt.Printf("INIT> %v\n", ir.GetMessage())
}))

// Build sketch for upload
sk := paths.New("testdata", "bare_minimum")
compile, err := grpcInst.Compile(context.Background(), "arduino:avr:uno", sk.String(), "")
require.NoError(t, err)
for {
msg, err := compile.Recv()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
fmt.Println("COMPILE ERROR>", err)
require.FailNow(t, "Expected successful compile", "compilation failed")
break
}
if msg.GetOutStream() != nil {
fmt.Printf("COMPILE OUT> %v\n", string(msg.GetOutStream()))
}
if msg.GetErrStream() != nil {
fmt.Printf("COMPILE ERR> %v\n", string(msg.GetErrStream()))
}
}

// Try upload and interrupt the call after 1 sec
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
upload, err := grpcInst.Upload(ctx, "arduino:avr:uno", sk.String(), "/dev/ttyACM0", "serial")
require.NoError(t, err)
checkFile := ""
for {
msg, err := upload.Recv()
if errors.Is(err, io.EOF) {
require.FailNow(t, "Expected interrupted upload", "upload succeeded")
break
}
if err != nil {
fmt.Println("UPLOAD ERROR>", err)
break
}
if out := string(msg.GetOutStream()); out != "" {
fmt.Printf("UPLOAD OUT> %v\n", out)
if strings.HasPrefix(out, "CHECKFILE: ") {
checkFile = strings.TrimSpace(out[11:])
}
}
if msg.GetErrStream() != nil {
fmt.Printf("UPLOAD ERR> %v\n", string(msg.GetErrStream()))
}
}
cancel()

// Wait 5 seconds.
// If the mocked avrdude is not killed it will create a checkfile and it will remove it after 5 seconds.
time.Sleep(5 * time.Second)

// Test if the checkfile is still there (if the file is there it means that mocked avrdude
// has been correctly killed).
require.NotEmpty(t, checkFile)
require.FileExists(t, checkFile)
require.NoError(t, os.Remove(checkFile))
}
1 change: 1 addition & 0 deletions internal/mock_avrdude/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mock_avrdude
46 changes: 46 additions & 0 deletions internal/mock_avrdude/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// This file is part arduino-cli.
//
// Copyright 2023 ARDUINO SA (http://www.arduino.cc/)
//
// This software is released under the GNU General Public License version 3,
// which covers the main part of arduino-cli.
// The terms of this license can be found at:
// https://www.gnu.org/licenses/gpl-3.0.en.html
//
// You can be released from the requirements of the above licenses by purchasing
// a commercial license. Buying such a license is mandatory if you want to modify or
// otherwise use the software for commercial activities involving the Arduino
// software without disclosing the source code of your own applications. To purchase
// a commercial license, send an email to license@arduino.cc.
//

package main

import (
"fmt"
"os"
"time"

"github.com/arduino/go-paths-helper"
)

func main() {
tmp, err := paths.MkTempFile(nil, "test")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
tmp.Close()
tmpPath := paths.New(tmp.Name())

fmt.Println("CHECKFILE:", tmpPath)

// Just sit here for 5 seconds
time.Sleep(5 * time.Second)

// Remove the check file at the end
tmpPath.Remove()

fmt.Println("COMPLETED")
}
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy