Skip to content

Commit fc25516

Browse files
authored
Merge pull request containerd#3918 from apostasie/ci-fix-flaky-signals
CI: rewrite signal tests
2 parents 6f608da + c2c1d4a commit fc25516

File tree

7 files changed

+172
-130
lines changed

7 files changed

+172
-130
lines changed

cmd/nerdctl/container/container_restart_linux_test.go

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package container
1818

1919
import (
2020
"fmt"
21+
"strconv"
2122
"strings"
2223
"testing"
2324
"time"
@@ -26,6 +27,7 @@ import (
2627

2728
"github.com/containerd/nerdctl/v2/pkg/testutil"
2829
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
30+
"github.com/containerd/nerdctl/v2/pkg/testutil/test"
2931
)
3032

3133
func TestRestart(t *testing.T) {
@@ -123,30 +125,38 @@ func TestRestartWithTime(t *testing.T) {
123125
}
124126

125127
func TestRestartWithSignal(t *testing.T) {
126-
t.Parallel()
127-
base := testutil.NewBase(t)
128-
tID := testutil.Identifier(t)
129-
130-
base.Cmd("run", "-d", "--name", tID, testutil.AlpineImage, "sh", "-c", `
131-
trap 'echo "Received SIGUSR1"; exit 0' SIGUSR1
132-
echo "Starting"
133-
while true; do
134-
sleep 1
135-
done
136-
`).AssertOK()
137-
defer base.Cmd("rm", "-f", tID).Run()
138-
139-
base.EnsureContainerStarted(tID)
140-
141-
inspect := base.InspectContainer(tID)
142-
initialPid := inspect.State.Pid
143-
144-
base.Cmd("restart", "--signal", "SIGUSR1", tID).AssertOK()
145-
base.EnsureContainerStarted(tID)
146-
147-
newInspect := base.InspectContainer(tID)
148-
newPid := newInspect.State.Pid
149-
150-
assert.Assert(t, initialPid != newPid, "Container PID should have changed after restart")
151-
128+
testCase := nerdtest.Setup()
129+
130+
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
131+
helpers.Anyhow("rm", "-f", data.Identifier())
132+
}
133+
134+
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
135+
cmd := nerdtest.RunSigProxyContainer(nerdtest.SigUsr1, false, nil, data, helpers)
136+
// Capture the current pid
137+
data.Set("oldpid", strconv.Itoa(nerdtest.InspectContainer(helpers, data.Identifier()).State.Pid))
138+
// Send the signal
139+
helpers.Ensure("restart", "--signal", "SIGUSR1", data.Identifier())
140+
return cmd
141+
}
142+
143+
testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {
144+
return &test.Expected{
145+
// Check the container did indeed exit
146+
ExitCode: 137,
147+
Output: test.All(
148+
// Check that we saw SIGUSR1 inside the container
149+
test.Contains(nerdtest.SignalCaught),
150+
func(stdout string, info string, t *testing.T) {
151+
// Ensure the container was restarted
152+
nerdtest.EnsureContainerStarted(helpers, data.Identifier())
153+
// Check the new pid is different
154+
newpid := strconv.Itoa(nerdtest.InspectContainer(helpers, data.Identifier()).State.Pid)
155+
assert.Assert(helpers.T(), newpid != data.Get("oldpid"), info)
156+
},
157+
),
158+
}
159+
}
160+
161+
testCase.Run(t)
152162
}

cmd/nerdctl/container/container_run_linux_test.go

Lines changed: 43 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import (
2828
"path/filepath"
2929
"strconv"
3030
"strings"
31-
"syscall"
3231
"testing"
3332
"time"
3433

@@ -366,78 +365,61 @@ func TestRunTTY(t *testing.T) {
366365
}
367366
}
368367

369-
func runSigProxy(t *testing.T, args ...string) (string, bool, bool) {
370-
t.Parallel()
371-
base := testutil.NewBase(t)
372-
testContainerName := testutil.Identifier(t)
373-
defer base.Cmd("rm", "-f", testContainerName).Run()
374-
375-
fullArgs := []string{"run"}
376-
fullArgs = append(fullArgs, args...)
377-
fullArgs = append(fullArgs,
378-
"--name",
379-
testContainerName,
380-
testutil.CommonImage,
381-
"sh",
382-
"-c",
383-
testutil.SigProxyTestScript,
384-
)
368+
func TestRunSigProxy(t *testing.T) {
369+
testCase := nerdtest.Setup()
385370

386-
result := base.Cmd(fullArgs...).Start()
387-
process := result.Cmd.Process
371+
testCase.SubTests = []*test.Case{
372+
{
373+
Description: "SigProxyDefault",
388374

389-
// Waits until we reach the trap command in the shell script, then sends SIGINT.
390-
time.Sleep(3 * time.Second)
391-
syscall.Kill(process.Pid, syscall.SIGINT)
375+
Cleanup: func(data test.Data, helpers test.Helpers) {
376+
helpers.Anyhow("rm", "-f", data.Identifier())
377+
},
392378

393-
// Waits until SIGINT is sent and responded to, then kills process to avoid timeout
394-
time.Sleep(3 * time.Second)
395-
process.Kill()
379+
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
380+
cmd := nerdtest.RunSigProxyContainer(os.Interrupt, true, nil, data, helpers)
381+
err := cmd.Signal(os.Interrupt)
382+
assert.NilError(helpers.T(), err)
383+
return cmd
384+
},
396385

397-
sigIntRecieved := strings.Contains(result.Stdout(), testutil.SigProxyTrueOut)
398-
timedOut := strings.Contains(result.Stdout(), testutil.SigProxyTimeoutMsg)
386+
Expected: test.Expects(0, nil, test.Contains(nerdtest.SignalCaught)),
387+
},
388+
{
389+
Description: "SigProxyTrue",
399390

400-
return result.Stdout(), sigIntRecieved, timedOut
401-
}
391+
Cleanup: func(data test.Data, helpers test.Helpers) {
392+
helpers.Anyhow("rm", "-f", data.Identifier())
393+
},
402394

403-
func TestRunSigProxy(t *testing.T) {
395+
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
396+
cmd := nerdtest.RunSigProxyContainer(os.Interrupt, true, []string{"--sig-proxy=true"}, data, helpers)
397+
err := cmd.Signal(os.Interrupt)
398+
assert.NilError(helpers.T(), err)
399+
return cmd
400+
},
404401

405-
type testCase struct {
406-
name string
407-
args []string
408-
want bool
409-
expectedOut string
410-
}
411-
testCases := []testCase{
412-
{
413-
name: "SigProxyDefault",
414-
args: []string{},
415-
want: true,
416-
expectedOut: testutil.SigProxyTrueOut,
417-
},
418-
{
419-
name: "SigProxyTrue",
420-
args: []string{"--sig-proxy=true"},
421-
want: true,
422-
expectedOut: testutil.SigProxyTrueOut,
402+
Expected: test.Expects(0, nil, test.Contains(nerdtest.SignalCaught)),
423403
},
424404
{
425-
name: "SigProxyFalse",
426-
args: []string{"--sig-proxy=false"},
427-
want: false,
428-
expectedOut: "",
405+
Description: "SigProxyFalse",
406+
407+
Cleanup: func(data test.Data, helpers test.Helpers) {
408+
helpers.Anyhow("rm", "-f", data.Identifier())
409+
},
410+
411+
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
412+
cmd := nerdtest.RunSigProxyContainer(os.Interrupt, true, []string{"--sig-proxy=false"}, data, helpers)
413+
err := cmd.Signal(os.Interrupt)
414+
assert.NilError(helpers.T(), err)
415+
return cmd
416+
},
417+
418+
Expected: test.Expects(127, nil, test.DoesNotContain(nerdtest.SignalCaught)),
429419
},
430420
}
431421

432-
for _, tc := range testCases {
433-
tc := tc
434-
t.Run(tc.name, func(t *testing.T) {
435-
stdout, sigIntRecieved, timedOut := runSigProxy(t, tc.args...)
436-
errorMsg := fmt.Sprintf("%s failed;\nExpected: '%s'\nActual: '%s'", tc.name, tc.expectedOut, stdout)
437-
assert.Equal(t, false, timedOut, errorMsg)
438-
assert.Equal(t, tc.want, sigIntRecieved, errorMsg)
439-
})
440-
}
422+
testCase.Run(t)
441423
}
442424

443425
func TestRunWithFluentdLogDriver(t *testing.T) {

cmd/nerdctl/container/container_stop_linux_test.go

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ import (
2929
"github.com/containerd/nerdctl/v2/pkg/rootlessutil"
3030
"github.com/containerd/nerdctl/v2/pkg/testutil"
3131
iptablesutil "github.com/containerd/nerdctl/v2/pkg/testutil/iptables"
32+
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
3233
"github.com/containerd/nerdctl/v2/pkg/testutil/nettestutil"
34+
"github.com/containerd/nerdctl/v2/pkg/testutil/test"
3335
)
3436

3537
func TestStopStart(t *testing.T) {
@@ -73,31 +75,23 @@ func TestStopStart(t *testing.T) {
7375
}
7476

7577
func TestStopWithStopSignal(t *testing.T) {
76-
t.Parallel()
77-
// There may be issues with logs in Docker.
78-
// This test is flaky with Docker. Might be related to https://github.com/containerd/nerdctl/pull/3557
79-
base := testutil.NewBase(t)
80-
testContainerName := testutil.Identifier(t)
81-
defer base.Cmd("rm", "-f", testContainerName).Run()
78+
testCase := nerdtest.Setup()
8279

83-
base.Cmd("run", "-d", "--stop-signal", "SIGQUIT", "--name", testContainerName, testutil.CommonImage, "sh", "-euxc", `#!/bin/sh
84-
set -eu
85-
echo "Script started"
86-
quit=0
87-
trap 'echo "SIGQUIT received"; quit=1' QUIT
88-
echo "Trap set"
89-
while true; do
90-
if [ $quit -eq 1 ]; then
91-
echo "Quitting loop"
92-
break
93-
fi
94-
echo "In loop"
95-
sleep 1
96-
done
97-
echo "signal quit"
98-
sync`).AssertOK()
99-
base.Cmd("stop", testContainerName).AssertOK()
100-
base.Cmd("logs", "-f", testContainerName).AssertOutContains("signal quit")
80+
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
81+
helpers.Anyhow("rm", "-f", data.Identifier())
82+
}
83+
84+
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
85+
cmd := nerdtest.RunSigProxyContainer(nerdtest.SigQuit, false,
86+
[]string{"--stop-signal", nerdtest.SigQuit.String()}, data, helpers)
87+
helpers.Ensure("stop", data.Identifier())
88+
return cmd
89+
}
90+
91+
// Verify that SIGQUIT was sent to the container AND that the container did forcefully exit
92+
testCase.Expected = test.Expects(137, nil, test.Contains(nerdtest.SignalCaught))
93+
94+
testCase.Run(t)
10195
}
10296

10397
func TestStopCleanupForwards(t *testing.T) {
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package nerdtest
18+
19+
import (
20+
"os"
21+
"strconv"
22+
"strings"
23+
"syscall"
24+
"time"
25+
26+
"github.com/containerd/nerdctl/v2/pkg/testutil"
27+
"github.com/containerd/nerdctl/v2/pkg/testutil/test"
28+
)
29+
30+
const SignalCaught = "received"
31+
32+
var SigQuit os.Signal = syscall.SIGQUIT
33+
var SigUsr1 os.Signal = syscall.SIGUSR1
34+
35+
func RunSigProxyContainer(signal os.Signal, exitOnSignal bool, args []string, data test.Data, helpers test.Helpers) test.TestableCommand {
36+
sig := strconv.Itoa(int(signal.(syscall.Signal)))
37+
ready := "trap ready"
38+
script := `#!/bin/sh
39+
set -eu
40+
41+
sig_msg () {
42+
printf "` + SignalCaught + `\n"
43+
[ "` + strconv.FormatBool(exitOnSignal) + `" != true ] || exit 0
44+
}
45+
46+
trap sig_msg ` + sig + `
47+
printf "` + ready + `\n"
48+
while true; do
49+
sleep 0.1
50+
done
51+
`
52+
53+
args = append(args, "--name", data.Identifier(), testutil.CommonImage, "sh", "-c", script)
54+
args = append([]string{"run"}, args...)
55+
56+
cmd := helpers.Command(args...)
57+
cmd.Background(10 * time.Second)
58+
EnsureContainerStarted(helpers, data.Identifier())
59+
60+
for {
61+
out := helpers.Capture("logs", data.Identifier())
62+
if strings.Contains(out, ready) {
63+
break
64+
}
65+
time.Sleep(100 * time.Millisecond)
66+
}
67+
68+
return cmd
69+
}

pkg/testutil/test/command.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,10 @@ func (gc *GenericCommand) Background(timeout time.Duration) {
185185
gc.result = icmd.StartCmd(i)
186186
}
187187

188+
func (gc *GenericCommand) Signal(sig os.Signal) error {
189+
return gc.result.Cmd.Process.Signal(sig)
190+
}
191+
188192
func (gc *GenericCommand) withEnv(env map[string]string) {
189193
if gc.Env == nil {
190194
gc.Env = map[string]string{}

pkg/testutil/test/test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ type TestableCommand interface {
114114
Run(expect *Expected)
115115
// Background allows starting a command in the background
116116
Background(timeout time.Duration)
117+
// Signal sends a signal to a backgrounded command
118+
Signal(sig os.Signal) error
117119
// Stderr allows retrieving the raw stderr output of the command
118120
Stderr() string
119121
}

pkg/testutil/testutil_linux.go

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -54,25 +54,6 @@ var (
5454
// It should be "connection refused" as per the TCP RFC.
5555
// https://www.rfc-editor.org/rfc/rfc793
5656
ExpectedConnectionRefusedError = "connection refused"
57-
58-
SigProxyTrueOut = "received SIGINT"
59-
SigProxyTimeoutMsg = "Timed Out; No signal received"
60-
SigProxyTestScript = `#!/bin/sh
61-
set -eu
62-
63-
sig_msg () {
64-
printf "` + SigProxyTrueOut + `"
65-
end
66-
}
67-
68-
trap sig_msg INT
69-
timeout=0
70-
while [ $timeout -ne 10 ]; do
71-
timeout=$((timeout+1))
72-
sleep 1
73-
done
74-
printf "` + SigProxyTimeoutMsg + `"
75-
end`
7657
)
7758

7859
const (

0 commit comments

Comments
 (0)