Skip to content

Commit 86e0d54

Browse files
authored
chore: add more tests and fuzzing (#1372)
Signed-off-by: egibs <20933572+egibs@users.noreply.github.com>
1 parent 63a0823 commit 86e0d54

File tree

18 files changed

+2332
-38
lines changed

18 files changed

+2332
-38
lines changed

Makefile

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ FIXERS += golangci-lint-fix
7676
golangci-lint-fix: $(GOLANGCI_LINT_BIN)
7777
find . -maxdepth 1 -name go.mod -execdir "$(GOLANGCI_LINT_BIN)" run -c "$(GOLANGCI_LINT_CONFIG)" --fix \;
7878

79+
FIXERS += modernize
80+
modernize:
81+
go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test ./...
82+
7983
LINTERS += yara-x-fmt
8084
yara-x-fmt: $(YARA_X_BIN)
8185
find rules -type f -name "*.yara" -execdir "$(YARA_X_BIN)" fmt {} \;
@@ -138,33 +142,18 @@ update-deps:
138142
test:
139143
go test -race ./pkg/...
140144

145+
FUZZ_TIME ?= 10s
141146
.PHONY: fuzz
142147
fuzz:
143-
go test -timeout 0 -fuzz=FuzzContainsUnprintable -fuzztime=10s ./pkg/report/
144-
go test -timeout 0 -fuzz=FuzzExtractArchive -fuzztime=10s ./pkg/archive/
145-
go test -timeout 0 -fuzz=FuzzExtractBz2 -fuzztime=10s ./pkg/archive/
146-
go test -timeout 0 -fuzz=FuzzExtractDeb -fuzztime=10s ./pkg/archive/
147-
go test -timeout 0 -fuzz=FuzzExtractGzip -fuzztime=10s ./pkg/archive/
148-
go test -timeout 0 -fuzz=FuzzExtractRPM -fuzztime=10s ./pkg/archive/
149-
go test -timeout 0 -fuzz=FuzzExtractTar -fuzztime=10s ./pkg/archive/
150-
go test -timeout 0 -fuzz=FuzzExtractUPX -fuzztime=10s ./pkg/archive/
151-
go test -timeout 0 -fuzz=FuzzExtractZip -fuzztime=10s ./pkg/archive/
152-
go test -timeout 0 -fuzz=FuzzExtractZlib -fuzztime=10s ./pkg/archive/
153-
go test -timeout 0 -fuzz=FuzzExtractZstd -fuzztime=10s ./pkg/archive/
154-
go test -timeout 0 -fuzz=FuzzFile -fuzztime=30s ./pkg/programkind/
155-
go test -timeout 0 -fuzz=FuzzGetExt -fuzztime=10s ./pkg/programkind/
156-
go test -timeout 0 -fuzz=FuzzIsValidPath -fuzztime=10s ./pkg/archive/
157-
go test -timeout 0 -fuzz=FuzzLongestUnique -fuzztime=10s ./pkg/report/
158-
go test -timeout 0 -fuzz=FuzzMatchToString -fuzztime=10s ./pkg/report/
159-
go test -timeout 0 -fuzz=FuzzPath -fuzztime=10s ./pkg/programkind/
160-
go test -timeout 0 -fuzz=FuzzRecursiveCompile -fuzztime=10s ./pkg/compile/
161-
go test -timeout 0 -fuzz=FuzzRemoveRules -fuzztime=10s ./pkg/compile/
162-
go test -timeout 0 -fuzz=FuzzRenderDifferential -fuzztime=10s ./pkg/render/
163-
go test -timeout 0 -fuzz=FuzzReportLoad -fuzztime=10s ./pkg/report/
164-
go test -timeout 0 -fuzz=FuzzStringPoolAtomic -fuzztime=10s ./pkg/report/
165-
go test -timeout 0 -fuzz=FuzzStringPoolConcurrent -fuzztime=10s ./pkg/report/
166-
go test -timeout 0 -fuzz=FuzzStringPoolIntern -fuzztime=10s ./pkg/report/
167-
go test -timeout 0 -fuzz=FuzzTrimPrefixes -fuzztime=10s ./pkg/report/
148+
@grep -r "^func Fuzz" --include="*_test.go" pkg/ | \
149+
awk -F'[:(]' '{gsub(/func /, "", $$2); dir=$$1; sub(/\/[^/]+$$/, "/", dir); print $$2, "./" dir}' | \
150+
while read -r func dir; do \
151+
echo "--- $$func ($$dir) ---"; \
152+
CGO_LDFLAGS="-L$(LINT_ROOT)/out/lib -Wl,-rpath,$(LINT_ROOT)/out/lib" \
153+
CGO_CPPFLAGS="-I$(LINT_ROOT)/out/include" \
154+
PKG_CONFIG_PATH="$(LINT_ROOT)/out/lib/pkgconfig" \
155+
go test -timeout 0 -fuzz="^$$func$$" -fuzztime=$(FUZZ_TIME) "$$dir" || exit 1; \
156+
done
168157

169158
# fuzz tests - runs continuously (use Ctrl+C to stop)
170159
# Usage: make fuzz-continuous FUZZ_TARGET=FuzzExtractArchive FUZZ_PKG=./pkg/archive/

pkg/action/fuzz_test.go

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// Copyright 2026 Chainguard, Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package action
5+
6+
import (
7+
"context"
8+
"os"
9+
"path/filepath"
10+
"strings"
11+
"sync"
12+
"testing"
13+
"time"
14+
15+
"github.com/chainguard-dev/malcontent/pkg/malcontent"
16+
)
17+
18+
// FuzzCleanPath fuzzes the CleanPath function.
19+
func FuzzCleanPath(f *testing.F) {
20+
f.Add("/tmp/extract/path/to/file", "/tmp/extract")
21+
f.Add("/tmp/extract2/file", "/tmp/extract")
22+
f.Add("/path", "")
23+
f.Add("", "/prefix")
24+
f.Add("/tmp/extract/file", "/tmp/extract")
25+
f.Add("/private/tmp/extract/file", "/private")
26+
f.Add("path\\with\\backslashes", "path")
27+
f.Add("/a/b/c", "/a/b")
28+
f.Add("/a/b/c", "/a/b/c")
29+
f.Add("/a/b/cd", "/a/b/c")
30+
31+
f.Fuzz(func(t *testing.T, path, prefix string) {
32+
result := CleanPath(path, prefix)
33+
34+
// Result never contains backslashes (normalized to /)
35+
if strings.Contains(result, "\\") {
36+
t.Errorf("CleanPath(%q, %q) = %q, contains backslash", path, prefix, result)
37+
}
38+
39+
// If prefix is empty, result is formatPath(input)
40+
if prefix == "" {
41+
want := strings.ReplaceAll(path, "\\", "/")
42+
if result != want {
43+
t.Errorf("CleanPath(%q, \"\") = %q, want %q", path, result, want)
44+
}
45+
}
46+
})
47+
}
48+
49+
// FuzzExitIfHitOrMiss fuzzes the exitIfHitOrMiss function.
50+
func FuzzExitIfHitOrMiss(f *testing.F) {
51+
f.Add("/scan/path", true, false, 0)
52+
f.Add("/scan/path", false, true, 0)
53+
f.Add("/scan/path", true, true, 2)
54+
f.Add("/scan/path", false, false, 3)
55+
f.Add("", true, false, 1)
56+
57+
f.Fuzz(func(t *testing.T, scanPath string, errIfHit, errIfMiss bool, numBehaviors int) {
58+
if numBehaviors < 0 || numBehaviors > 10 {
59+
return
60+
}
61+
62+
// nil map always returns nil, nil
63+
fr, err := exitIfHitOrMiss(nil, scanPath, errIfHit, errIfMiss)
64+
if fr != nil || err != nil {
65+
t.Fatal("nil map should return nil, nil")
66+
}
67+
68+
// When both flags are false, always returns nil, nil
69+
m := &sync.Map{}
70+
behaviors := make([]*malcontent.Behavior, numBehaviors)
71+
for i := range numBehaviors {
72+
behaviors[i] = &malcontent.Behavior{ID: "test"}
73+
}
74+
m.Store("file", &malcontent.FileReport{Path: "file", Behaviors: behaviors})
75+
76+
fr2, err2 := exitIfHitOrMiss(m, scanPath, false, false)
77+
if fr2 != nil || err2 != nil {
78+
t.Fatal("both flags false should return nil, nil")
79+
}
80+
})
81+
}
82+
83+
// FuzzFindFilesRecursively tests recursive file discovery with fuzzed
84+
// directory structures to ensure no panics, no .git inclusion, and
85+
// graceful handling of symlinks and broken links.
86+
func FuzzFindFilesRecursively(f *testing.F) {
87+
f.Add(3, 2, true, true)
88+
f.Add(0, 0, false, false)
89+
f.Add(5, 3, true, false)
90+
f.Add(1, 0, false, true)
91+
92+
f.Fuzz(func(t *testing.T, numFiles, numDirs int, createSymlink, createGitDir bool) {
93+
if numFiles < 0 || numFiles > 20 || numDirs < 0 || numDirs > 5 {
94+
return
95+
}
96+
97+
tmpDir, err := os.MkdirTemp("", "fuzz-find-*")
98+
if err != nil {
99+
t.Skip()
100+
}
101+
defer os.RemoveAll(tmpDir)
102+
103+
// Create subdirectories
104+
for i := range numDirs {
105+
subDir := filepath.Join(tmpDir, "dir"+string(rune('a'+i)))
106+
os.MkdirAll(subDir, 0o755)
107+
// Put a file in each subdir
108+
os.WriteFile(filepath.Join(subDir, "file.txt"), []byte("data"), 0o644)
109+
}
110+
111+
// Create files at root
112+
for i := range numFiles {
113+
os.WriteFile(filepath.Join(tmpDir, "file"+string(rune('0'+i))+".txt"), []byte("data"), 0o644)
114+
}
115+
116+
// Optionally create a .git directory (should be excluded from results)
117+
if createGitDir {
118+
gitDir := filepath.Join(tmpDir, ".git")
119+
os.MkdirAll(gitDir, 0o755)
120+
os.WriteFile(filepath.Join(gitDir, "config"), []byte("gitconfig"), 0o644)
121+
}
122+
123+
// Optionally create a symlink (symlinked dirs should be excluded)
124+
if createSymlink && numDirs > 0 {
125+
target := filepath.Join(tmpDir, "dir"+string(rune('a')))
126+
os.Symlink(target, filepath.Join(tmpDir, "link_to_dir"))
127+
}
128+
129+
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
130+
defer cancel()
131+
132+
files, err := findFilesRecursively(ctx, tmpDir)
133+
if err != nil {
134+
return // errors are OK (e.g., permission issues)
135+
}
136+
137+
// Invariant: no .git files in results
138+
for _, f := range files {
139+
if strings.Contains(f, "/.git/") {
140+
t.Errorf("found .git file in results: %q", f)
141+
}
142+
}
143+
})
144+
}

0 commit comments

Comments
 (0)