Skip to content

Commit 9597740

Browse files
committed
review
1 parent 4ec7afc commit 9597740

File tree

2 files changed

+100
-99
lines changed

2 files changed

+100
-99
lines changed

pkg/goformat/runner.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -251,15 +251,13 @@ func (o RunnerOptions) MatchAnyPattern(path string) (bool, error) {
251251
return false, nil
252252
}
253253

254-
// Resolve symlinks in the file path to ensure filepath.Rel works correctly
255-
// when running inside symlinked directories. The basePath is already resolved
256-
// via fsutils.Getwd() in NewRunnerOptions.
257-
evalPath, err := fsutils.EvalSymlinks(path)
254+
// The basePath is already resolved via `fsutils.Getwd()` in `NewRunnerOptions`.
255+
evaluatedPath, err := fsutils.EvalSymlinks(path)
258256
if err != nil {
259257
return false, err
260258
}
261259

262-
rel, err := filepath.Rel(o.basePath, evalPath)
260+
rel, err := filepath.Rel(o.basePath, evaluatedPath)
263261
if err != nil {
264262
return false, err
265263
}

pkg/goformat/runner_test.go

Lines changed: 97 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -3,129 +3,132 @@ package goformat
33
import (
44
"os"
55
"path/filepath"
6-
"regexp"
76
"testing"
87

98
"github.com/stretchr/testify/assert"
109
"github.com/stretchr/testify/require"
1110

12-
"github.com/golangci/golangci-lint/v2/pkg/fsutils"
11+
"github.com/golangci/golangci-lint/v2/pkg/config"
1312
)
1413

15-
func TestMatchAnyPattern_WithSymlinks(t *testing.T) {
16-
// Create a temporary directory structure
17-
tmpDir, err := os.MkdirTemp("", "golangci-symlink-test-*")
18-
require.NoError(t, err)
19-
defer os.RemoveAll(tmpDir)
14+
func TestRunnerOptions_MatchAnyPattern(t *testing.T) {
15+
testCases := []struct {
16+
desc string
17+
cfg *config.Config
18+
filename string
19+
20+
assertMatch assert.BoolAssertionFunc
21+
expectedCount int
22+
}{
23+
{
24+
desc: "match",
25+
cfg: &config.Config{
26+
Formatters: config.Formatters{
27+
Exclusions: config.FormatterExclusions{
28+
Paths: []string{`generated\.go`},
29+
},
30+
},
31+
},
32+
filename: "generated.go",
33+
assertMatch: assert.True,
34+
expectedCount: 1,
35+
},
36+
{
37+
desc: "no match",
38+
cfg: &config.Config{
39+
Formatters: config.Formatters{
40+
Exclusions: config.FormatterExclusions{
41+
Paths: []string{`excluded\.go`},
42+
},
43+
},
44+
},
45+
filename: "test.go",
46+
assertMatch: assert.False,
47+
expectedCount: 0,
48+
},
49+
{
50+
desc: "no patterns",
51+
cfg: &config.Config{},
52+
filename: "test.go",
53+
assertMatch: assert.False,
54+
},
55+
}
2056

21-
// Create two separate directories at the same level
22-
realDir := filepath.Join(tmpDir, "real_project")
23-
err = os.MkdirAll(realDir, 0o755)
24-
require.NoError(t, err)
57+
for _, test := range testCases {
58+
t.Run(test.desc, func(t *testing.T) {
59+
t.Parallel()
2560

26-
testFile := filepath.Join(realDir, "test.go")
27-
err = os.WriteFile(testFile, []byte("package main"), 0o600)
28-
require.NoError(t, err)
61+
tmpDir := t.TempDir()
2962

30-
// Create a symlink in a completely different part of the tree
31-
symlinkParent := filepath.Join(tmpDir, "symlinks")
32-
err = os.MkdirAll(symlinkParent, 0o755)
33-
require.NoError(t, err)
63+
testFile := filepath.Join(tmpDir, test.filename)
3464

35-
symlinkDir := filepath.Join(symlinkParent, "project_link")
36-
err = os.Symlink(realDir, symlinkDir)
37-
require.NoError(t, err)
65+
err := os.WriteFile(testFile, []byte("package main"), 0o600)
66+
require.NoError(t, err)
3867

39-
// Simulate the actual scenario:
40-
// - basePath is the resolved real path (as fsutils.Getwd does when you're in a symlinked dir)
41-
// - filepath.Walk from the symlink directory provides unresolved paths
42-
// IMPORTANT: On macOS, tmpDir might be /var/... which is itself a symlink to /private/var/...
43-
// So we need to evaluate basePath as well to simulate what fsutils.Getwd() does
44-
resolvedBasePath, err := fsutils.EvalSymlinks(realDir)
45-
require.NoError(t, err)
68+
test.cfg.SetConfigDir(tmpDir)
4669

47-
// Create RunnerOptions with a pattern that matches files in the root directory only
48-
// This pattern will match "test.go" but NOT "../../../test.go" or similar broken paths
49-
pattern := regexp.MustCompile(`^[^/\\]+\.go$`)
50-
opts := RunnerOptions{
51-
basePath: resolvedBasePath, // Resolved path from Getwd
52-
patterns: []*regexp.Regexp{pattern},
53-
excludedPathCounter: map[*regexp.Regexp]int{pattern: 0},
54-
}
70+
opts, err := NewRunnerOptions(test.cfg, false, false, false)
71+
require.NoError(t, err)
5572

56-
// filepath.Walk would provide the path through the symlink
57-
// When you cd into a symlink and run the command, Walk uses the symlink path
58-
unresolvedFile := filepath.Join(symlinkDir, "test.go")
59-
60-
// The issue: When basePath is resolved (e.g., /private/var/...)
61-
// but the file path from filepath.Walk is unresolved (e.g., /var/...),
62-
// filepath.Rel produces an incorrect relative path with many ../ components
63-
// like "../../../../var/.../test.go" which won't match the pattern ^[^/\\]+\.go$
64-
//
65-
// The fix: EvalSymlinks on the file path before calling filepath.Rel
66-
// ensures both paths are in their canonical form, producing "test.go"
67-
// which correctly matches the pattern.
68-
69-
match, err := opts.MatchAnyPattern(unresolvedFile)
70-
71-
// With the fix, pattern matching should work correctly
72-
require.NoError(t, err, "Should not error when matching pattern with symlinks")
73-
assert.True(t, match, "Pattern should match test.go when accessed through symlink")
74-
assert.Equal(t, 1, opts.excludedPathCounter[pattern], "Counter should be incremented")
75-
}
73+
match, err := opts.MatchAnyPattern(testFile)
74+
require.NoError(t, err)
7675

77-
func TestMatchAnyPattern_NoPatterns(t *testing.T) {
78-
opts := RunnerOptions{
79-
basePath: "/tmp",
80-
patterns: []*regexp.Regexp{},
81-
excludedPathCounter: map[*regexp.Regexp]int{},
82-
}
76+
test.assertMatch(t, match)
8377

84-
match, err := opts.MatchAnyPattern("/tmp/test.go")
85-
require.NoError(t, err)
86-
assert.False(t, match, "Should not match when no patterns are defined")
78+
require.Len(t, opts.patterns, len(test.cfg.Formatters.Exclusions.Paths))
79+
80+
if len(opts.patterns) == 0 {
81+
assert.Empty(t, opts.excludedPathCounter)
82+
} else {
83+
assert.Equal(t, test.expectedCount, opts.excludedPathCounter[opts.patterns[0]])
84+
}
85+
})
86+
}
8787
}
8888

89-
func TestMatchAnyPattern_NoMatch(t *testing.T) {
90-
tmpDir, err := os.MkdirTemp("", "golangci-test-*")
89+
func TestRunnerOptions_MatchAnyPattern_withSymlinks(t *testing.T) {
90+
tmpDir := t.TempDir()
91+
92+
testFile := filepath.Join(tmpDir, "project", "test.go")
93+
94+
realDir := filepath.Dir(testFile)
95+
96+
err := os.MkdirAll(realDir, 0o755)
9197
require.NoError(t, err)
92-
defer os.RemoveAll(tmpDir)
9398

94-
testFile := filepath.Join(tmpDir, "test.go")
9599
err = os.WriteFile(testFile, []byte("package main"), 0o600)
96100
require.NoError(t, err)
97101

98-
pattern := regexp.MustCompile(`excluded\.go`)
99-
opts := RunnerOptions{
100-
basePath: tmpDir,
101-
patterns: []*regexp.Regexp{pattern},
102-
excludedPathCounter: map[*regexp.Regexp]int{pattern: 0},
103-
}
102+
// Create a symlink in a completely different part of the tree.
103+
symlink := filepath.Join(tmpDir, "somewhere", "symlink")
104104

105-
match, err := opts.MatchAnyPattern(testFile)
105+
err = os.MkdirAll(filepath.Dir(symlink), 0o755)
106106
require.NoError(t, err)
107-
assert.False(t, match, "Pattern should not match test.go")
108-
assert.Equal(t, 0, opts.excludedPathCounter[pattern], "Counter should not be incremented")
109-
}
110107

111-
func TestMatchAnyPattern_WithMatch(t *testing.T) {
112-
tmpDir, err := os.MkdirTemp("", "golangci-test-*")
108+
err = os.Symlink(realDir, symlink)
113109
require.NoError(t, err)
114-
defer os.RemoveAll(tmpDir)
115110

116-
testFile := filepath.Join(tmpDir, "generated.go")
117-
err = os.WriteFile(testFile, []byte("package main"), 0o600)
118-
require.NoError(t, err)
119-
120-
pattern := regexp.MustCompile(`generated\.go`)
121-
opts := RunnerOptions{
122-
basePath: tmpDir,
123-
patterns: []*regexp.Regexp{pattern},
124-
excludedPathCounter: map[*regexp.Regexp]int{pattern: 0},
111+
cfg := &config.Config{
112+
Formatters: config.Formatters{
113+
Exclusions: config.FormatterExclusions{
114+
// Creates a pattern that matches files in the root directory only.
115+
// This pattern will match `test.go" but not `../../../test.go` or similar broken paths.
116+
Paths: []string{`^[^/\\]+\.go$`},
117+
},
118+
},
125119
}
120+
cfg.SetConfigDir(realDir)
121+
122+
opts, err := NewRunnerOptions(cfg, false, false, false)
123+
require.NoError(t, err)
126124

127-
match, err := opts.MatchAnyPattern(testFile)
125+
match, err := opts.MatchAnyPattern(filepath.Join(symlink, "test.go"))
128126
require.NoError(t, err)
129-
assert.True(t, match, "Pattern should match generated.go")
130-
assert.Equal(t, 1, opts.excludedPathCounter[pattern], "Counter should be incremented")
127+
128+
assert.True(t, match)
129+
130+
require.NotEmpty(t, opts.patterns)
131+
require.Len(t, opts.patterns, len(cfg.Formatters.Exclusions.Paths))
132+
133+
assert.Equal(t, 1, opts.excludedPathCounter[opts.patterns[0]])
131134
}

0 commit comments

Comments
 (0)