Skip to content

Commit b4c3bf9

Browse files
authored
Merge pull request #1649 from antitree/main
feat: Add linter to check for potentially hanging tests
2 parents e3eb49c + 6b5bbe7 commit b4c3bf9

10 files changed

+377
-0
lines changed

pkg/lint/rules.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,28 @@ import (
2121
)
2222

2323
var (
24+
daemonFlags = []string{
25+
`(?:^|\s)--daemon\b`,
26+
`(?:^|\s)--daemonize\b`,
27+
`(?:^|\s)--detach\b`,
28+
`(?:^|\s)-daemon\b`,
29+
}
30+
31+
redirPatterns = []string{
32+
`>\s*\S+`,
33+
`>>\s*\S+`,
34+
`2>\s*\S+`,
35+
`2>>\s*\S+`,
36+
`&>\s*\S+`,
37+
`&>>\s*\S+`,
38+
`>\s*\S+.*2>&1`,
39+
`2>&1.*>\s*\S+`,
40+
`>\s*/dev/null`,
41+
`2>\s*/dev/null`,
42+
`&>\s*/dev/null`,
43+
`\d+>&\d+`,
44+
}
45+
2446
reValidSHA256 = regexp.MustCompile(`^[a-fA-F0-9]{64}$`)
2547
reValidSHA512 = regexp.MustCompile(`^[a-fA-F0-9]{128}$`)
2648
reValidSHA1 = regexp.MustCompile(`^[a-fA-F0-9]{40}$`)
@@ -43,6 +65,14 @@ var (
4365
hostEditDistanceExceptions = map[string]string{
4466
"www.libssh.org": "www.libssh2.org",
4567
}
68+
69+
// Detect background processes (commands ending with '&' or '& sleep ...') or daemonized commands
70+
// reBackgroundProcess detects background processes (commands ending with '&' or '& sleep ...')
71+
// We explicitly avoid matching '&&' which is commonly used for command chaining.
72+
reBackgroundProcess = regexp.MustCompile(`(?:^|[^&])&(?:\s*$|\s+sleep\b)`) // matches 'cmd &' or 'cmd & sleep'
73+
reDaemonProcess = regexp.MustCompile(`.*(?:` + strings.Join(daemonFlags, "|") + `).*`)
74+
// Detect output redirection in shell commands
75+
reOutputRedirect = regexp.MustCompile(strings.Join(redirPatterns, "|"))
4676
)
4777

4878
const gitCheckout = "git-checkout"
@@ -456,6 +486,47 @@ var AllRules = func(l *Linter) Rules { //nolint:gocyclo
456486
return fmt.Errorf("auto-update is disabled but no reason is provided")
457487
},
458488
},
489+
{
490+
Name: "background-process-without-redirect",
491+
Description: "test steps should redirect output when running background processes",
492+
Severity: SeverityWarning,
493+
LintFunc: func(c config.Configuration) error {
494+
checkSteps := func(steps []config.Pipeline) error {
495+
for _, s := range steps {
496+
if s.Runs == "" {
497+
continue
498+
}
499+
lines := strings.Split(s.Runs, "\n")
500+
for i, line := range lines {
501+
checkLine := line
502+
if strings.Contains(line, "&") && i+1 < len(lines) {
503+
checkLine += "\n" + lines[i+1]
504+
}
505+
506+
needsRedirect := reBackgroundProcess.MatchString(checkLine) || reDaemonProcess.MatchString(line)
507+
if needsRedirect && !reOutputRedirect.MatchString(line) {
508+
return fmt.Errorf("background process missing output redirect: %s", strings.TrimSpace(line))
509+
}
510+
}
511+
}
512+
return nil
513+
}
514+
515+
if c.Test != nil {
516+
if err := checkSteps(c.Test.Pipeline); err != nil {
517+
return err
518+
}
519+
}
520+
for _, sp := range c.Subpackages {
521+
if sp.Test != nil {
522+
if err := checkSteps(sp.Test.Pipeline); err != nil {
523+
return err
524+
}
525+
}
526+
}
527+
return nil
528+
},
529+
},
459530
{
460531
Name: "valid-update-schedule",
461532
Description: "update schedule config should contain a valid period",

pkg/lint/rules_test.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,95 @@ func TestLinter_Rules(t *testing.T) {
469469
wantErr: true,
470470
matches: 1,
471471
},
472+
{
473+
file: "background-process-no-redirect.yaml",
474+
minSeverity: SeverityWarning,
475+
want: EvalResult{
476+
File: "background-process-no-redirect",
477+
Errors: EvalRuleErrors{
478+
{
479+
Rule: Rule{
480+
Name: "background-process-without-redirect",
481+
Severity: SeverityWarning,
482+
},
483+
Error: fmt.Errorf("[background-process-without-redirect]: background process missing output redirect: croc relay --ports=1234 & (WARNING)"),
484+
},
485+
},
486+
},
487+
wantErr: false,
488+
matches: 1,
489+
},
490+
{
491+
file: "background-process-multiline-no-redirect.yaml",
492+
minSeverity: SeverityWarning,
493+
want: EvalResult{
494+
File: "background-process-multiline-no-redirect",
495+
Errors: EvalRuleErrors{
496+
{
497+
Rule: Rule{
498+
Name: "background-process-without-redirect",
499+
Severity: SeverityWarning,
500+
},
501+
Error: fmt.Errorf("[background-process-without-redirect]: background process missing output redirect: coredns & (WARNING)"),
502+
},
503+
},
504+
},
505+
wantErr: false,
506+
matches: 1,
507+
},
508+
{
509+
file: "background-process-with-redirect.yaml",
510+
minSeverity: SeverityWarning,
511+
want: EvalResult{},
512+
wantErr: false,
513+
matches: 0,
514+
},
515+
{
516+
file: "double-ampersand-valid.yaml",
517+
minSeverity: SeverityWarning,
518+
want: EvalResult{},
519+
wantErr: false,
520+
matches: 0,
521+
},
522+
{
523+
file: "daemon-flag-no-redirect.yaml",
524+
minSeverity: SeverityWarning,
525+
want: EvalResult{
526+
File: "daemon-flag-no-redirect",
527+
Errors: EvalRuleErrors{
528+
{
529+
Rule: Rule{
530+
Name: "background-process-without-redirect",
531+
Severity: SeverityWarning,
532+
},
533+
Error: fmt.Errorf("[background-process-without-redirect]: background process missing output redirect: croc relay --daemon (WARNING)"),
534+
},
535+
},
536+
},
537+
wantErr: false,
538+
matches: 1,
539+
},
540+
{
541+
file: "daemon-flag-with-redirect.yaml",
542+
minSeverity: SeverityWarning,
543+
want: EvalResult{},
544+
wantErr: false,
545+
matches: 0,
546+
},
547+
{
548+
file: "avahi-no-daemon.yaml",
549+
minSeverity: SeverityWarning,
550+
want: EvalResult{},
551+
wantErr: false,
552+
matches: 0,
553+
},
554+
{
555+
file: "cut-d-flag.yaml",
556+
minSeverity: SeverityWarning,
557+
want: EvalResult{},
558+
wantErr: false,
559+
matches: 0,
560+
},
472561
}
473562

474563
for _, tt := range tests {
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package:
2+
name: avahi-no-daemon
3+
version: 1.0.0
4+
epoch: 0
5+
description: Package running avahi commands without backgrounding
6+
copyright:
7+
- paths:
8+
- "*"
9+
attestation: TODO
10+
license: GPL-2.0-only
11+
pipeline:
12+
- uses: fetch
13+
with:
14+
uri: https://test.com/avahi/${{package.version}}.tar.gz
15+
expected-sha256: ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269
16+
test:
17+
pipeline:
18+
# AUTOGENERATED
19+
- runs: |
20+
avahi-browse --version
21+
avahi-browse-domains --version
22+
avahi-publish --version
23+
avahi-publish-address --version
24+
avahi-publish-service --version
25+
avahi-resolve --version
26+
avahi-resolve-address --version
27+
avahi-resolve-host-name --version
28+
avahi-set-host-name --version
29+
avahi-autoipd --version
30+
avahi-daemon --version
31+
avahi-dnsconfd --version
32+
avahi-browse --help
33+
avahi-browse-domains --help
34+
avahi-publish --help
35+
avahi-publish-address --help
36+
avahi-publish-service --help
37+
avahi-resolve --help
38+
avahi-resolve-address --help
39+
avahi-resolve-host-name --help
40+
avahi-set-host-name --help
41+
avahi-autoipd --help
42+
avahi-daemon --help
43+
avahi-dnsconfd --help
44+
update:
45+
enabled: true
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package:
2+
name: background-process-multiline-no-redirect
3+
version: 1.0.0
4+
epoch: 0
5+
description: Package with multiline background process without redirect
6+
copyright:
7+
- paths:
8+
- "*"
9+
attestation: TODO
10+
license: GPL-2.0-only
11+
pipeline:
12+
- uses: fetch
13+
with:
14+
uri: https://test.com/background/${{package.version}}.tar.gz
15+
expected-sha256: ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269
16+
test:
17+
pipeline:
18+
- runs: |
19+
cat > Corefile <<EOF
20+
.:1053 {
21+
file /home/build/db.wolfi.dev
22+
log
23+
errors
24+
cache
25+
}
26+
EOF
27+
28+
cat > /home/build/db.wolfi.dev <<'EOF'
29+
$TTL 3600
30+
@ IN SOA ns1.wolfi.dev. admin.wolfi.dev. (
31+
20240101 ; Serial
32+
7200 ; Refresh
33+
3600 ; Retry
34+
1209600 ; Expire
35+
3600 ) ; Negative Cache TTL
36+
;
37+
@ IN NS ns1.wolfi.dev.
38+
;
39+
foo.wolfi.dev IN TXT "hi"
40+
EOF
41+
42+
coredns &
43+
sleep 2
44+
update:
45+
enabled: true
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package:
2+
name: background-process-no-redirect
3+
version: 1.0.0
4+
epoch: 0
5+
description: Package with background process without redirect
6+
copyright:
7+
- paths:
8+
- "*"
9+
attestation: TODO
10+
license: GPL-2.0-only
11+
pipeline:
12+
- uses: fetch
13+
with:
14+
uri: https://test.com/background/${{package.version}}.tar.gz
15+
expected-sha256: ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269
16+
test:
17+
pipeline:
18+
- runs: "croc relay --ports=1234 &"
19+
update:
20+
enabled: true
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package:
2+
name: background-process-with-redirect
3+
version: 1.0.0
4+
epoch: 0
5+
description: Package with background process with redirect
6+
copyright:
7+
- paths:
8+
- "*"
9+
attestation: TODO
10+
license: GPL-2.0-only
11+
pipeline:
12+
- uses: fetch
13+
with:
14+
uri: https://test.com/background/${{package.version}}.tar.gz
15+
expected-sha256: ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269
16+
test:
17+
pipeline:
18+
- runs: "croc relay --ports=1234 > croc.log 2>&1 &"
19+
update:
20+
enabled: true
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package:
2+
name: cut-d-flag
3+
version: 1.0.0
4+
epoch: 0
5+
description: Package using cut -d but not running daemon
6+
copyright:
7+
- paths:
8+
- "*"
9+
attestation: TODO
10+
license: GPL-2.0-only
11+
pipeline:
12+
- uses: fetch
13+
with:
14+
uri: https://test.com/cut/${{package.version}}.tar.gz
15+
expected-sha256: ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269
16+
test:
17+
pipeline:
18+
- runs: "getcap /usr/bin/fping | cut -d ' ' -f2 | grep -q -E '^cap_net_raw=+ep$'"
19+
update:
20+
enabled: true
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package:
2+
name: daemon-flag-no-redirect
3+
version: 1.0.0
4+
epoch: 0
5+
description: Package with daemon flag without redirect
6+
copyright:
7+
- paths:
8+
- "*"
9+
attestation: TODO
10+
license: GPL-2.0-only
11+
pipeline:
12+
- uses: fetch
13+
with:
14+
uri: https://test.com/daemon/${{package.version}}.tar.gz
15+
expected-sha256: ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269
16+
test:
17+
pipeline:
18+
- runs: "croc relay --daemon"
19+
update:
20+
enabled: true
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package:
2+
name: daemon-flag-with-redirect
3+
version: 1.0.0
4+
epoch: 0
5+
description: Package with daemon flag and redirect
6+
copyright:
7+
- paths:
8+
- "*"
9+
attestation: TODO
10+
license: GPL-2.0-only
11+
pipeline:
12+
- uses: fetch
13+
with:
14+
uri: https://test.com/daemon/${{package.version}}.tar.gz
15+
expected-sha256: ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269
16+
test:
17+
pipeline:
18+
- runs: "croc relay --daemon > croc.log 2>&1"
19+
update:
20+
enabled: true

0 commit comments

Comments
 (0)