Skip to content

Commit c3edbd3

Browse files
[skip-changelog] Avoid getting locked up in a perpetual symlink loop when loading a library (#2146)
* Improve library examples loading process by filtering out unneeded directories * Add tests for the changes
1 parent 855c238 commit c3edbd3

File tree

8 files changed

+93
-12
lines changed

8 files changed

+93
-12
lines changed

Diff for: arduino/libraries/libraries_test.go

+68
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ package libraries
1717

1818
import (
1919
"encoding/json"
20+
"os"
2021
"testing"
22+
"time"
2123

2224
paths "github.com/arduino/go-paths-helper"
2325
"github.com/stretchr/testify/require"
@@ -85,3 +87,69 @@ func TestLibrariesLoader(t *testing.T) {
8587
require.True(t, lib.IsLegacy)
8688
}
8789
}
90+
91+
func TestSymlinkLoop(t *testing.T) {
92+
// Set up directory structure of test library.
93+
testLib := paths.New("testdata", "TestLib")
94+
examplesPath := testLib.Join("examples")
95+
require.NoError(t, examplesPath.Mkdir())
96+
defer examplesPath.RemoveAll()
97+
98+
// It's probably most friendly for contributors using Windows to create the symlinks needed for the test on demand.
99+
err := os.Symlink(examplesPath.Join("..").String(), examplesPath.Join("UpGoer1").String())
100+
require.NoError(t, err, "This test must be run as administrator on Windows to have symlink creation privilege.")
101+
// It's necessary to have multiple symlinks to a parent directory to create the loop.
102+
err = os.Symlink(examplesPath.Join("..").String(), examplesPath.Join("UpGoer2").String())
103+
require.NoError(t, err)
104+
105+
// The failure condition is Load() never returning, testing for which requires setting up a timeout.
106+
done := make(chan bool)
107+
go func() {
108+
_, err = Load(testLib, User)
109+
done <- true
110+
}()
111+
select {
112+
case <-done:
113+
case <-time.After(2 * time.Second):
114+
require.FailNow(t, "Load didn't complete in the allocated time.")
115+
}
116+
require.Error(t, err)
117+
}
118+
119+
func TestLegacySymlinkLoop(t *testing.T) {
120+
// Set up directory structure of test library.
121+
testLib := paths.New("testdata", "LegacyLib")
122+
examplesPath := testLib.Join("examples")
123+
require.NoError(t, examplesPath.Mkdir())
124+
defer examplesPath.RemoveAll()
125+
126+
// It's probably most friendly for contributors using Windows to create the symlinks needed for the test on demand.
127+
err := os.Symlink(examplesPath.Join("..").String(), examplesPath.Join("UpGoer1").String())
128+
require.NoError(t, err, "This test must be run as administrator on Windows to have symlink creation privilege.")
129+
// It's necessary to have multiple symlinks to a parent directory to create the loop.
130+
err = os.Symlink(examplesPath.Join("..").String(), examplesPath.Join("UpGoer2").String())
131+
require.NoError(t, err)
132+
133+
// The failure condition is Load() never returning, testing for which requires setting up a timeout.
134+
done := make(chan bool)
135+
go func() {
136+
_, err = Load(testLib, User)
137+
done <- true
138+
}()
139+
140+
select {
141+
case <-done:
142+
case <-time.After(2 * time.Second):
143+
require.FailNow(t, "Load didn't complete in the allocated time.")
144+
}
145+
require.Error(t, err)
146+
}
147+
148+
func TestLoadExamples(t *testing.T) {
149+
example, err := paths.New(".", "testdata", "TestLibExamples", "examples", "simple").Abs()
150+
require.NoError(t, err)
151+
lib, err := Load(paths.New("testdata", "TestLibExamples"), User)
152+
require.NoError(t, err)
153+
require.Len(t, lib.Examples, 1)
154+
require.True(t, lib.Examples.Contains(example))
155+
}

Diff for: arduino/libraries/loader.go

+16-12
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020
"strings"
2121

2222
"github.com/arduino/arduino-cli/arduino/globals"
23-
"github.com/arduino/arduino-cli/arduino/sketch"
2423
"github.com/arduino/go-paths-helper"
2524
properties "github.com/arduino/go-properties-orderedmap"
2625
"github.com/pkg/errors"
@@ -176,20 +175,11 @@ func addExamples(lib *Library) error {
176175
}
177176

178177
func addExamplesToPathList(examplesPath *paths.Path, list *paths.PathList) error {
179-
files, err := examplesPath.ReadDir()
178+
files, err := examplesPath.ReadDirRecursiveFiltered(nil, paths.AndFilter(paths.FilterDirectories(), filterExamplesDirs))
180179
if err != nil {
181180
return err
182181
}
183-
for _, file := range files {
184-
_, err := sketch.New(file)
185-
if err == nil {
186-
list.Add(file)
187-
} else if file.IsDir() {
188-
if err := addExamplesToPathList(file, list); err != nil {
189-
return err
190-
}
191-
}
192-
}
182+
list.AddAll(files)
193183
return nil
194184
}
195185

@@ -208,3 +198,17 @@ func containsHeaderFile(d *paths.Path) (bool, error) {
208198
dirContent.FilterSuffix(headerExtensions...)
209199
return len(dirContent) > 0, nil
210200
}
201+
202+
// filterExamplesDirs filters out any directory that does not contain a ".ino" or ".pde" file
203+
// with the correct casing
204+
func filterExamplesDirs(dir *paths.Path) bool {
205+
if list, err := dir.ReadDir(); err == nil {
206+
list.FilterOutDirs()
207+
list.FilterPrefix(dir.Base()+".ino", dir.Base()+".pde")
208+
// accept only directories that contain a single correct sketch
209+
if list.Len() == 1 {
210+
return true
211+
}
212+
}
213+
return false
214+
}

Diff for: arduino/libraries/testdata/TestLibExamples/examples/MultipleFiles/MultipleFiles.ino

Whitespace-only changes.

Diff for: arduino/libraries/testdata/TestLibExamples/examples/MultipleFiles/MultipleFiles.pde

Whitespace-only changes.

Diff for: arduino/libraries/testdata/TestLibExamples/examples/WrongCasing/wrongCasing.ino

Whitespace-only changes.

Diff for: arduino/libraries/testdata/TestLibExamples/examples/simple/simple.ino

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
name=TestLib
2+
version=1.0.3
3+
author=Arduino
4+
maintainer=Arduino <[email protected]>
5+
sentence=A test lib
6+
paragraph=very powerful!
7+
category=Device Control
8+
url=http://www.arduino.cc/en/Reference/TestLib
9+
architectures=avr

Diff for: arduino/libraries/testdata/TestLibExamples/src/TestLib.h

Whitespace-only changes.

0 commit comments

Comments
 (0)