Skip to content

Commit

Permalink
tests: add plugin-socket-compatibility tests
Browse files Browse the repository at this point in the history
Adds a new plugin to the e2e plugins that simulates an older
plugin binary and a test suite to ensure older plugin binaries
keep behaving the same with newer CLI versions.

Signed-off-by: Laura Brehm <[email protected]>
(cherry picked from commit cfa9fef)
Signed-off-by: Sebastiaan van Stijn <[email protected]>
  • Loading branch information
laurazard authored and thaJeztah committed Feb 6, 2024
1 parent 2f6b5ad commit 1cbc218
Show file tree
Hide file tree
Showing 3 changed files with 366 additions and 4 deletions.
12 changes: 8 additions & 4 deletions cli-plugins/socket/socket_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net"
"os"
"runtime"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -49,17 +50,17 @@ func TestSetupConn(t *testing.T) {
listener, err := SetupConn(&conn)
assert.NilError(t, err)
assert.Check(t, listener != nil, "returned nil listener but no error")
checkDirClean(t)
checkDirNoPluginSocket(t)

addr, err := net.ResolveUnixAddr("unix", listener.Addr().String())
assert.NilError(t, err, "failed to resolve listener address")
_, err = net.DialUnix("unix", nil, addr)
assert.NilError(t, err, "failed to dial returned listener")
checkDirClean(t)
checkDirNoPluginSocket(t)
})
}

func checkDirClean(t *testing.T) {
func checkDirNoPluginSocket(t *testing.T) {
t.Helper()

files, err := os.ReadDir(".")
Expand All @@ -68,7 +69,8 @@ func checkDirClean(t *testing.T) {
for _, f := range files {
info, err := f.Info()
assert.NilError(t, err, "failed to check file info")
if info.Mode().Type() == fs.ModeSocket {
// check for a socket with `docker_cli_` in the name (from `SetupConn()`)
if strings.Contains(f.Name(), "docker_cli_") && info.Mode().Type() == fs.ModeSocket {
t.Fatal("found socket in a local directory")
}
}
Expand Down Expand Up @@ -96,6 +98,8 @@ func TestConnectAndWait(t *testing.T) {
}
})

// TODO: this test cannot be executed with `t.Parallel()`, due to
// relying on goroutine numbers to ensure correct behaviour
t.Run("connect goroutine exits after EOF", func(t *testing.T) {
var conn *net.UnixConn
listener, err := SetupConn(&conn)
Expand Down
123 changes: 123 additions & 0 deletions e2e/cli-plugins/plugins/presocket/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package main

import (
"fmt"
"os"
"os/signal"
"syscall"
"time"

"github.com/docker/cli/cli-plugins/manager"
"github.com/docker/cli/cli-plugins/plugin"
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
)

func main() {
plugin.Run(RootCmd, manager.Metadata{
SchemaVersion: "0.1.0",
Vendor: "Docker Inc.",
Version: "test",
})
}

func RootCmd(dockerCli command.Cli) *cobra.Command {
cmd := cobra.Command{
Use: "presocket",
Short: "testing plugin that does not connect to the socket",
// override PersistentPreRunE so that the plugin default
// PersistentPreRunE doesn't run, simulating a plugin built
// with a pre-socket-communication version of the CLI
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
return nil
},
}

cmd.AddCommand(&cobra.Command{
Use: "test-no-socket",
Short: "test command that runs until it receives a SIGINT",
RunE: func(cmd *cobra.Command, args []string) error {
go func() {
<-cmd.Context().Done()
fmt.Fprintln(dockerCli.Out(), "context cancelled")
os.Exit(2)
}()
signalCh := make(chan os.Signal, 10)
signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM)
go func() {
for range signalCh {
fmt.Fprintln(dockerCli.Out(), "received SIGINT")
}
}()
<-time.After(3 * time.Second)
fmt.Fprintln(dockerCli.Err(), "exit after 3 seconds")
return nil
},
})

cmd.AddCommand(&cobra.Command{
Use: "test-socket",
Short: "test command that runs until it receives a SIGINT",
PreRunE: func(cmd *cobra.Command, args []string) error {
return plugin.PersistentPreRunE(cmd, args)
},
RunE: func(cmd *cobra.Command, args []string) error {
go func() {
<-cmd.Context().Done()
fmt.Fprintln(dockerCli.Out(), "context cancelled")
os.Exit(2)
}()
signalCh := make(chan os.Signal, 10)
signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM)
go func() {
for range signalCh {
fmt.Fprintln(dockerCli.Out(), "received SIGINT")
}
}()
<-time.After(3 * time.Second)
fmt.Fprintln(dockerCli.Err(), "exit after 3 seconds")
return nil
},
})

cmd.AddCommand(&cobra.Command{
Use: "test-socket-ignore-context",
Short: "test command that runs until it receives a SIGINT",
PreRunE: func(cmd *cobra.Command, args []string) error {
return plugin.PersistentPreRunE(cmd, args)
},
RunE: func(cmd *cobra.Command, args []string) error {
signalCh := make(chan os.Signal, 10)
signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM)
go func() {
for range signalCh {
fmt.Fprintln(dockerCli.Out(), "received SIGINT")
}
}()
<-time.After(3 * time.Second)
fmt.Fprintln(dockerCli.Err(), "exit after 3 seconds")
return nil
},
})

cmd.AddCommand(&cobra.Command{
Use: "tty",
Short: "test command that attempts to read from the TTY",
RunE: func(cmd *cobra.Command, args []string) error {
done := make(chan struct{})
go func() {
b := make([]byte, 1)
_, _ = dockerCli.In().Read(b)
done <- struct{}{}
}()
select {
case <-done:
case <-time.After(2 * time.Second):
fmt.Fprint(dockerCli.Err(), "timeout after 2 seconds")
}
return nil
},
})

return &cmd
}
Loading

0 comments on commit 1cbc218

Please sign in to comment.