Skip to content

Commit 3ccdb9d

Browse files
authored
Fixed compile error when sketch has a broken symlink (#2497)
* Removed unneeded pointer * Improved error message * Update go-paths library * Added integration tests * Reduced timeout for symlink-loop tests * Fixed unit tests Previously the unit tests were creating the wrong env to test: internal/arduino/libraries/testdata/TestLib ├── examples │   ├── UpGoer1 -> testdata/TestLib │   └── UpGoer2 -> testdata/TestLib ├── library.properties └── src └── TestLib.h The two UpGoer1 and UpGoer2 are broken links. The correct tree is the following: internal/arduino/libraries/testdata/TestLib ├── examples │   ├── UpGoer1 -> .. │   └── UpGoer2 -> .. ├── library.properties └── src └── TestLib.h that actually triggers the symlink loop we are testing. * Fixed integration test * Removed apparently useless check for "readable" files
1 parent 8115da1 commit 3ccdb9d

File tree

14 files changed

+104
-26
lines changed

14 files changed

+104
-26
lines changed

Diff for: .licenses/go/github.com/arduino/go-paths-helper.dep.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
name: github.com/arduino/go-paths-helper
3-
version: v1.11.0
3+
version: v1.12.0
44
type: go
55
summary:
66
homepage: https://pkg.go.dev/github.com/arduino/go-paths-helper

Diff for: go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ replace github.com/mailru/easyjson => github.com/cmaglie/easyjson v0.8.1
77

88
require (
99
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371
10-
github.com/arduino/go-paths-helper v1.11.0
10+
github.com/arduino/go-paths-helper v1.12.0
1111
github.com/arduino/go-properties-orderedmap v1.8.0
1212
github.com/arduino/go-timeutils v0.0.0-20171220113728-d1dd9e313b1b
1313
github.com/arduino/go-win32-utils v1.0.0

Diff for: go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ
1111
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
1212
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
1313
github.com/arduino/go-paths-helper v1.0.1/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3v5YYu35Yb+w31Ck=
14-
github.com/arduino/go-paths-helper v1.11.0 h1:hkpGb9AtCTByTj2FKutuHWb3klDf4kAKL10hW+fN+oE=
15-
github.com/arduino/go-paths-helper v1.11.0/go.mod h1:jcpW4wr0u69GlXhTYydsdsqAjLaYK5n7oWHfKqOG6LM=
14+
github.com/arduino/go-paths-helper v1.12.0 h1:xizOQtI9iHdl19qXd1EmWg5i9W//2bOCOYwlNv8F61E=
15+
github.com/arduino/go-paths-helper v1.12.0/go.mod h1:jcpW4wr0u69GlXhTYydsdsqAjLaYK5n7oWHfKqOG6LM=
1616
github.com/arduino/go-properties-orderedmap v1.8.0 h1:wEfa6hHdpezrVOh787OmClsf/Kd8qB+zE3P2Xbrn0CQ=
1717
github.com/arduino/go-properties-orderedmap v1.8.0/go.mod h1:DKjD2VXY/NZmlingh4lSFMEYCVubfeArCsGPGDwb2yk=
1818
github.com/arduino/go-timeutils v0.0.0-20171220113728-d1dd9e313b1b h1:9hDi4F2st6dbLC3y4i02zFT5quS4X6iioWifGlVwfy4=

Diff for: internal/arduino/libraries/libraries_test.go

+8-6
Original file line numberDiff line numberDiff line change
@@ -95,15 +95,16 @@ func TestLibrariesLoader(t *testing.T) {
9595
func TestSymlinkLoop(t *testing.T) {
9696
// Set up directory structure of test library.
9797
testLib := paths.New("testdata", "TestLib")
98-
examplesPath := testLib.Join("examples")
98+
examplesPath, err := testLib.Join("examples").Abs()
99+
require.NoError(t, err)
99100
require.NoError(t, examplesPath.Mkdir())
100101
defer examplesPath.RemoveAll()
101102

102103
// It's probably most friendly for contributors using Windows to create the symlinks needed for the test on demand.
103-
err := os.Symlink(examplesPath.Join("..").String(), examplesPath.Join("UpGoer1").String())
104+
err = os.Symlink(examplesPath.String(), examplesPath.Join("UpGoer1").String())
104105
require.NoError(t, err, "This test must be run as administrator on Windows to have symlink creation privilege.")
105106
// It's necessary to have multiple symlinks to a parent directory to create the loop.
106-
err = os.Symlink(examplesPath.Join("..").String(), examplesPath.Join("UpGoer2").String())
107+
err = os.Symlink(examplesPath.String(), examplesPath.Join("UpGoer2").String())
107108
require.NoError(t, err)
108109

109110
// The failure condition is Load() never returning, testing for which requires setting up a timeout.
@@ -123,15 +124,16 @@ func TestSymlinkLoop(t *testing.T) {
123124
func TestLegacySymlinkLoop(t *testing.T) {
124125
// Set up directory structure of test library.
125126
testLib := paths.New("testdata", "LegacyLib")
126-
examplesPath := testLib.Join("examples")
127+
examplesPath, err := testLib.Join("examples").Abs()
128+
require.NoError(t, err)
127129
require.NoError(t, examplesPath.Mkdir())
128130
defer examplesPath.RemoveAll()
129131

130132
// It's probably most friendly for contributors using Windows to create the symlinks needed for the test on demand.
131-
err := os.Symlink(examplesPath.Join("..").String(), examplesPath.Join("UpGoer1").String())
133+
err = os.Symlink(examplesPath.String(), examplesPath.Join("UpGoer1").String())
132134
require.NoError(t, err, "This test must be run as administrator on Windows to have symlink creation privilege.")
133135
// It's necessary to have multiple symlinks to a parent directory to create the loop.
134-
err = os.Symlink(examplesPath.Join("..").String(), examplesPath.Join("UpGoer2").String())
136+
err = os.Symlink(examplesPath.String(), examplesPath.Join("UpGoer2").String())
135137
require.NoError(t, err)
136138

137139
// The failure condition is Load() never returning, testing for which requires setting up a timeout.

Diff for: internal/arduino/sketch/sketch.go

+4-11
Original file line numberDiff line numberDiff line change
@@ -111,18 +111,11 @@ func New(path *paths.Path) (*Sketch, error) {
111111

112112
sketchFolderFiles, err := sketch.supportedFiles()
113113
if err != nil {
114-
return nil, err
114+
return nil, fmt.Errorf("%s: %w", tr("reading sketch files"), err)
115115
}
116116

117117
// Collect files
118-
for _, p := range *sketchFolderFiles {
119-
// Skip files that can't be opened
120-
f, err := p.Open()
121-
if err != nil {
122-
continue
123-
}
124-
f.Close()
125-
118+
for _, p := range sketchFolderFiles {
126119
ext := p.Ext()
127120
if globals.MainFileValidExtensions[ext] {
128121
if p.EqualsTo(mainFile) {
@@ -160,7 +153,7 @@ func New(path *paths.Path) (*Sketch, error) {
160153

161154
// supportedFiles reads all files recursively contained in Sketch and
162155
// filter out unneded or unsupported ones and returns them
163-
func (s *Sketch) supportedFiles() (*paths.PathList, error) {
156+
func (s *Sketch) supportedFiles() (paths.PathList, error) {
164157
filterValidExtensions := func(p *paths.Path) bool {
165158
return globals.MainFileValidExtensions[p.Ext()] || globals.AdditionalFileValidExtensions[p.Ext()]
166159
}
@@ -180,7 +173,7 @@ func (s *Sketch) supportedFiles() (*paths.PathList, error) {
180173
if err != nil {
181174
return nil, err
182175
}
183-
return &files, nil
176+
return files, nil
184177
}
185178

186179
// GetProfile returns the requested profile or an error if not found

Diff for: internal/arduino/sketch/sketch_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ func TestNewSketchWithSymlinkLoop(t *testing.T) {
341341
return false
342342
}
343343
},
344-
20*time.Second,
344+
5*time.Second,
345345
10*time.Millisecond,
346346
"Infinite symlink loop while loading sketch",
347347
)
@@ -380,7 +380,7 @@ func TestSketchWithMultipleSymlinkLoops(t *testing.T) {
380380
return false
381381
}
382382
},
383-
20*time.Second,
383+
5*time.Second,
384384
10*time.Millisecond,
385385
"Infinite symlink loop while loading sketch",
386386
)

Diff for: internal/integrationtest/compile_1/compile_test.go

+23-3
Original file line numberDiff line numberDiff line change
@@ -220,16 +220,36 @@ func compileWithSketchWithSymlinkSelfloop(t *testing.T, env *integrationtest.Env
220220
require.NoError(t, err)
221221
require.Contains(t, string(stdout), "Sketch created in: "+sketchPath.String())
222222

223-
// create a symlink that loops on himself
223+
// Create a symlink that loops on himself
224+
//
225+
// /tmp/cli2843369229/Arduino/CompileIntegrationTestSymlinkSelfLoop
226+
// ├── CompileIntegrationTestSymlinkSelfLoop.ino
227+
// └── loop -> /tmp/cli2843369229/Arduino/CompileIntegrationTestSymlinkSelfLoop/loop
228+
//
229+
// in this case the link is "broken", and it will be ignored by the compiler
224230
loopFilePath := sketchPath.Join("loop")
225231
err = os.Symlink(loopFilePath.String(), loopFilePath.String())
226232
require.NoError(t, err)
227233

228-
// Build sketch for arduino:avr:uno
234+
_, _, err = cli.Run("compile", "-b", fqbn, sketchPath.String())
235+
require.NoError(t, err)
236+
237+
// Add a symlink that loops on himself named as a .ino file
238+
//
239+
// /tmp/cli2843369229/Arduino/CompileIntegrationTestSymlinkSelfLoop
240+
// ├── CompileIntegrationTestSymlinkSelfLoop.ino
241+
// ├── loop -> /tmp/cli2843369229/Arduino/CompileIntegrationTestSymlinkSelfLoop/loop
242+
// └── loop.ino -> /tmp/cli2843369229/Arduino/CompileIntegrationTestSymlinkSelfLoop/loop.ino
243+
//
244+
// in this case the new link is "broken" as before, but being part of the sketch will trigger an error.
245+
loopInoFilePath := sketchPath.Join("loop.ino")
246+
err = os.Symlink(loopFilePath.String(), loopInoFilePath.String())
247+
require.NoError(t, err)
248+
229249
_, stderr, err := cli.Run("compile", "-b", fqbn, sketchPath.String())
230250
// The assertion is a bit relaxed in this case because win behaves differently from macOs and linux
231251
// returning a different error detailed message
232-
require.Contains(t, string(stderr), "Can't open sketch:")
252+
require.Contains(t, string(stderr), "Error during build:")
233253
require.Error(t, err)
234254
}
235255
{
+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// This file is part of arduino-cli.
2+
//
3+
// Copyright 2023 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This software is released under the GNU General Public License version 3,
6+
// which covers the main part of arduino-cli.
7+
// The terms of this license can be found at:
8+
// https://www.gnu.org/licenses/gpl-3.0.en.html
9+
//
10+
// You can be released from the requirements of the above licenses by purchasing
11+
// a commercial license. Buying such a license is mandatory if you want to
12+
// modify or otherwise use the software for commercial activities involving the
13+
// Arduino software without disclosing the source code of your own applications.
14+
// To purchase a commercial license, send an email to [email protected].
15+
16+
package compile_test
17+
18+
import (
19+
"testing"
20+
21+
"github.com/arduino/arduino-cli/internal/integrationtest"
22+
"github.com/arduino/go-paths-helper"
23+
"github.com/stretchr/testify/require"
24+
)
25+
26+
func TestCompileWithBrokenSymLinks(t *testing.T) {
27+
env, cli := integrationtest.CreateArduinoCLIWithEnvironment(t)
28+
t.Cleanup(env.CleanUp)
29+
30+
// Install Arduino AVR Boards
31+
_, _, err := cli.Run("core", "install", "arduino:[email protected]")
32+
require.NoError(t, err)
33+
34+
t.Run("NonSketchFileBroken", func(t *testing.T) {
35+
sketch, err := paths.New("testdata", "ValidSketchWithBrokenSymlink").Abs()
36+
require.NoError(t, err)
37+
_, _, err = cli.Run("compile", "-b", "arduino:avr:uno", sketch.String())
38+
require.NoError(t, err)
39+
})
40+
41+
t.Run("SketchFileBroken", func(t *testing.T) {
42+
sketch, err := paths.New("testdata", "ValidSketchWithBrokenSketchFileSymlink").Abs()
43+
require.NoError(t, err)
44+
_, _, err = cli.Run("compile", "-b", "arduino:avr:uno", sketch.String())
45+
require.Error(t, err)
46+
})
47+
48+
t.Run("NonInoSketchFileBroken", func(t *testing.T) {
49+
sketch, err := paths.New("testdata", "ValidSketchWithNonInoBrokenSketchFileSymlink").Abs()
50+
require.NoError(t, err)
51+
_, _, err = cli.Run("compile", "-b", "arduino:avr:uno", sketch.String())
52+
require.Error(t, err)
53+
})
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
void setup() {}
2+
void loop() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
broken
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
void setup() {}
2+
void loop() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
broken
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
void setup() {}
2+
void loop() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
broken

0 commit comments

Comments
 (0)