Skip to content

Commit 09ea608

Browse files
authored
test(go): refactor mod_test.go to use txtar format (#9775)
1 parent 2c3aca5 commit 09ea608

File tree

23 files changed

+286
-210
lines changed

23 files changed

+286
-210
lines changed

go.mod

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ require (
111111
github.com/twitchtv/twirp v8.1.3+incompatible
112112
github.com/xeipuuv/gojsonschema v1.2.0
113113
github.com/xlab/treeprint v1.2.0
114+
github.com/zalando/go-keyring v0.2.6
114115
github.com/zclconf/go-cty v1.17.0
115116
github.com/zclconf/go-cty-yaml v1.1.0
116117
go.etcd.io/bbolt v1.4.3
@@ -120,6 +121,7 @@ require (
120121
golang.org/x/sync v0.17.0
121122
golang.org/x/term v0.35.0
122123
golang.org/x/text v0.28.0
124+
golang.org/x/tools v0.35.1-0.20250728180453-01a3475a31bc
123125
golang.org/x/vuln v1.1.4
124126
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9
125127
google.golang.org/protobuf v1.36.10
@@ -130,8 +132,6 @@ require (
130132
modernc.org/sqlite v1.39.0
131133
)
132134

133-
require github.com/zalando/go-keyring v0.2.6
134-
135135
require (
136136
al.essio.dev/pkg/shellescape v1.5.1 // indirect
137137
buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.6-20250718181942-e35f9b667443.1 // indirect
@@ -471,7 +471,6 @@ require (
471471
golang.org/x/sys v0.36.0 // indirect
472472
golang.org/x/telemetry v0.0.0-20250807160809-1a19826ec488 // indirect
473473
golang.org/x/time v0.13.0 // indirect
474-
golang.org/x/tools v0.35.1-0.20250728180453-01a3475a31bc // indirect
475474
golang.org/x/tools/gopls v0.20.0 // indirect
476475
google.golang.org/api v0.248.0 // indirect
477476
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect

internal/testutil/txtar.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package testutil
2+
3+
import (
4+
"io/fs"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
"golang.org/x/tools/txtar"
9+
)
10+
11+
// TxtarToFS reads a txtar file and returns it as an fs.FS.
12+
func TxtarToFS(t *testing.T, path string) fs.FS {
13+
t.Helper()
14+
archive, err := txtar.ParseFile(path)
15+
require.NoError(t, err)
16+
fsys, err := txtar.FS(archive)
17+
require.NoError(t, err)
18+
return fsys
19+
}

pkg/fanal/analyzer/language/golang/mod/mod.go

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"io"
1010
"io/fs"
1111
"os"
12+
"path"
1213
"path/filepath"
1314
"regexp"
1415
"slices"
@@ -53,6 +54,10 @@ type gomodAnalyzer struct {
5354

5455
licenseClassifierConfidenceLevel float64
5556

57+
// gopathFS represents the $GOPATH directory as an fs.FS.
58+
// It should contain the "pkg/mod" subdirectory structure.
59+
gopathFS fs.FS
60+
5661
logger *log.Logger
5762
}
5863

@@ -62,6 +67,7 @@ func newGoModAnalyzer(opt analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, erro
6267
sumParser: sum.NewParser(),
6368
leafModParser: mod.NewParser(false, false), // Don't detect stdlib for non-root go.mod files
6469
licenseClassifierConfidenceLevel: opt.LicenseScannerOption.ClassifierConfidenceLevel,
70+
gopathFS: os.DirFS(cmp.Or(os.Getenv("GOPATH"), build.Default.GOPATH)),
6571
logger: log.WithPrefix("golang"),
6672
}, nil
6773
}
@@ -142,7 +148,7 @@ func (a *gomodAnalyzer) fillAdditionalData(ctx context.Context, fsys fs.FS, apps
142148
var modSearchDirs []searchDir
143149

144150
// $GOPATH/pkg/mod
145-
if gopath, err := newGOPATH(); err != nil {
151+
if gopath, err := newGOPATH(a.gopathFS); err != nil {
146152
a.logger.Debug("GOPATH not found. Run 'go mod download' or 'go mod tidy' for identifying dependency graph and licenses", log.Err(err))
147153
} else {
148154
modSearchDirs = append(modSearchDirs, gopath)
@@ -413,18 +419,26 @@ type searchDir interface {
413419
}
414420

415421
type gopathDir struct {
416-
root string
422+
root fs.FS // $GOPATH/pkg/mod as fs.FS (can be os.DirFS or test fixture)
417423
}
418424

419-
func newGOPATH() (searchDir, error) {
420-
gopath := cmp.Or(os.Getenv("GOPATH"), build.Default.GOPATH)
421-
425+
func newGOPATH(gopathFS fs.FS) (searchDir, error) {
422426
// $GOPATH/pkg/mod
423-
modPath := filepath.Join(gopath, "pkg", "mod")
424-
if !fsutils.DirExists(modPath) {
425-
return nil, xerrors.Errorf("GOPATH not found: %s", modPath)
427+
// Use path.Join instead of filepath.Join because fs.FS always uses forward slashes,
428+
// regardless of the operating system.
429+
modFS, err := fs.Sub(gopathFS, path.Join("pkg", "mod"))
430+
if err != nil {
431+
return nil, xerrors.Errorf("failed to access $GOPATH/pkg/mod: %w", err)
432+
}
433+
434+
// Check if the directory exists.
435+
// fs.Sub doesn't return an error for non-existent directories,
436+
// so we need to explicitly verify the directory exists.
437+
if _, err := fs.Stat(modFS, "."); err != nil {
438+
return nil, xerrors.Errorf("$GOPATH/pkg/mod does not exist: %w", err)
426439
}
427-
return &gopathDir{root: modPath}, nil
440+
441+
return &gopathDir{root: modFS}, nil
428442
}
429443

430444
// Resolve resolves the module directory for a given package.
@@ -437,9 +451,7 @@ func (d *gopathDir) Resolve(pkg types.Package) (fs.FS, error) {
437451
// e.g. github.com/aquasecurity/[email protected]
438452
modDirName := fmt.Sprintf("%s@%s", name, pkg.Version)
439453

440-
// e.g. $GOPATH/pkg/mod/github.com/aquasecurity/[email protected]
441-
modDir := filepath.Join(d.root, modDirName)
442-
return os.DirFS(modDir), nil
454+
return fs.Sub(d.root, modDirName)
443455
}
444456

445457
type vendorDir struct {
@@ -451,8 +463,16 @@ func newVendorDir(fsys fs.FS, modPath string) (vendorDir, error) {
451463
vendor := filepath.Join(filepath.Dir(modPath), "vendor")
452464
sub, err := fs.Sub(fsys, vendor)
453465
if err != nil {
454-
return vendorDir{}, xerrors.Errorf("vendor directory not found: %w", err)
466+
return vendorDir{}, xerrors.Errorf("failed to access vendor directory: %w", err)
467+
}
468+
469+
// Check if the directory exists.
470+
// fs.Sub doesn't return an error for non-existent directories,
471+
// so we need to explicitly verify the directory exists.
472+
if _, err := fs.Stat(sub, "."); err != nil {
473+
return vendorDir{}, xerrors.Errorf("vendor directory does not exist: %w", err)
455474
}
475+
456476
return vendorDir{root: sub}, nil
457477
}
458478

pkg/fanal/analyzer/language/golang/mod/mod_test.go

Lines changed: 42 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,30 @@
11
package mod
22

33
import (
4-
"path/filepath"
54
"sort"
65
"testing"
76

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

10+
"github.com/aquasecurity/trivy/internal/testutil"
1111
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
1212
"github.com/aquasecurity/trivy/pkg/fanal/types"
13-
"github.com/aquasecurity/trivy/pkg/mapfs"
1413
)
1514

15+
const gopathFixture = "testdata/gopath.txtar"
16+
1617
func Test_gomodAnalyzer_Analyze(t *testing.T) {
1718
tests := []struct {
18-
name string
19-
files []string
20-
want *analyzer.AnalysisResult
19+
name string
20+
txtar string
21+
gopath bool
22+
want *analyzer.AnalysisResult
2123
}{
2224
{
23-
name: "happy",
24-
files: []string{
25-
"testdata/happy/mod",
26-
"testdata/happy/sum",
27-
},
25+
name: "happy",
26+
txtar: "testdata/happy.txtar",
27+
gopath: true,
2828
want: &analyzer.AnalysisResult{
2929
Applications: []types.Application{
3030
{
@@ -74,10 +74,9 @@ func Test_gomodAnalyzer_Analyze(t *testing.T) {
7474
},
7575
},
7676
{
77-
name: "wrong go.mod from `pkg`",
78-
files: []string{
79-
"testdata/wrong-gomod-in-pkg/mod",
80-
},
77+
name: "wrong go.mod from `pkg`",
78+
txtar: "testdata/wrong-gomod-in-pkg.txtar",
79+
gopath: true,
8180
want: &analyzer.AnalysisResult{
8281
Applications: []types.Application{
8382
{
@@ -116,10 +115,9 @@ func Test_gomodAnalyzer_Analyze(t *testing.T) {
116115
},
117116
},
118117
{
119-
name: "no pkg dir found",
120-
files: []string{
121-
"testdata/no-pkg-found/mod",
122-
},
118+
name: "no pkg dir found",
119+
txtar: "testdata/no-pkg-found.txtar",
120+
gopath: false,
123121
want: &analyzer.AnalysisResult{
124122
Applications: []types.Application{
125123
{
@@ -179,11 +177,9 @@ func Test_gomodAnalyzer_Analyze(t *testing.T) {
179177
},
180178
},
181179
{
182-
name: "less than 1.17",
183-
files: []string{
184-
"testdata/merge/mod",
185-
"testdata/merge/sum",
186-
},
180+
name: "less than 1.17",
181+
txtar: "testdata/merge.txtar",
182+
gopath: true,
187183
want: &analyzer.AnalysisResult{
188184
Applications: []types.Application{
189185
{
@@ -235,10 +231,9 @@ func Test_gomodAnalyzer_Analyze(t *testing.T) {
235231
},
236232
},
237233
{
238-
name: "no go.sum",
239-
files: []string{
240-
"testdata/merge/mod",
241-
},
234+
name: "no go.sum",
235+
txtar: "testdata/no-go-sum.txtar",
236+
gopath: true,
242237
want: &analyzer.AnalysisResult{
243238
Applications: []types.Application{
244239
{
@@ -278,18 +273,15 @@ func Test_gomodAnalyzer_Analyze(t *testing.T) {
278273
},
279274
},
280275
{
281-
name: "sad go.mod",
282-
files: []string{
283-
"testdata/sad/mod",
284-
},
285-
want: &analyzer.AnalysisResult{},
276+
name: "sad go.mod",
277+
txtar: "testdata/sad.txtar",
278+
gopath: false,
279+
want: &analyzer.AnalysisResult{},
286280
},
287281
{
288-
name: "deps from GOPATH and license from vendor dir",
289-
files: []string{
290-
"testdata/vendor-dir-exists/mod",
291-
"testdata/vendor-dir-exists/vendor",
292-
},
282+
name: "deps from GOPATH and license from vendor dir",
283+
txtar: "testdata/vendor-dir-exists.txtar",
284+
gopath: true,
293285
want: &analyzer.AnalysisResult{
294286
Applications: []types.Application{
295287
{
@@ -339,36 +331,34 @@ func Test_gomodAnalyzer_Analyze(t *testing.T) {
339331
},
340332
},
341333
}
334+
335+
// Load GOPATH fixture once as fs.FS (represents $GOPATH/pkg/mod)
336+
gopathFS := testutil.TxtarToFS(t, gopathFixture)
337+
342338
for _, tt := range tests {
343-
t.Setenv("GOPATH", "testdata")
344339
t.Run(tt.name, func(t *testing.T) {
340+
// Load test case txtar as fs.FS
341+
fsys := testutil.TxtarToFS(t, tt.txtar)
342+
345343
a, err := newGoModAnalyzer(analyzer.AnalyzerOptions{})
346344
require.NoError(t, err)
347345

348-
mfs := mapfs.New()
349-
for _, file := range tt.files {
350-
// Since broken go.mod files bothers IDE, we should use other file names than "go.mod" and "go.sum".
351-
switch filepath.Base(file) {
352-
case "mod":
353-
require.NoError(t, mfs.WriteFile("go.mod", file))
354-
case "sum":
355-
require.NoError(t, mfs.WriteFile("go.sum", file))
356-
case "vendor":
357-
require.NoError(t, mfs.CopyDir(file, "."))
358-
}
346+
// Set GOPATH fs.FS for testing
347+
ma := a.(*gomodAnalyzer)
348+
if tt.gopath {
349+
ma.gopathFS = gopathFS
359350
}
360351

361352
ctx := t.Context()
362-
got, err := a.PostAnalyze(ctx, analyzer.PostAnalysisInput{
363-
FS: mfs,
353+
got, err := ma.PostAnalyze(ctx, analyzer.PostAnalysisInput{
354+
FS: fsys,
364355
})
365356
require.NoError(t, err)
366357

367358
if len(got.Applications) > 0 {
368359
sort.Sort(got.Applications[0].Packages)
369360
sort.Sort(tt.want.Applications[0].Packages)
370361
}
371-
require.NoError(t, err)
372362
assert.Equal(t, tt.want, got)
373363
})
374364
}

0 commit comments

Comments
 (0)