TestPluginSocketBackwardsCompatible executes a plugin binary that does not connect to the CLI plugin socket, simulating a plugin compiled against an older version of the CLI, and ensures that backwards compatibility is maintained.
(t *testing.T)
| 19 | // a plugin compiled against an older version of the CLI, and |
| 20 | // ensures that backwards compatibility is maintained. |
| 21 | func TestPluginSocketBackwardsCompatible(t *testing.T) { |
| 22 | run, _, cleanup := prepare(t) |
| 23 | defer cleanup() |
| 24 | |
| 25 | t.Run("attached", func(t *testing.T) { |
| 26 | t.Run("the plugin gets signalled if attached to a TTY", func(t *testing.T) { |
| 27 | cmd := run("presocket", "test-no-socket") |
| 28 | command := exec.Command(cmd.Command[0], cmd.Command[1:]...) |
| 29 | |
| 30 | ptmx, err := pty.Start(command) |
| 31 | assert.NilError(t, err, "failed to launch command with fake TTY") |
| 32 | |
| 33 | // send a SIGINT to the process group after 1 second, since |
| 34 | // we're simulating an "attached TTY" scenario and a TTY would |
| 35 | // send a signal to the process group |
| 36 | go func() { |
| 37 | <-time.After(time.Second) |
| 38 | err := syscall.Kill(-command.Process.Pid, syscall.SIGINT) |
| 39 | assert.NilError(t, err, "failed to signal process group") |
| 40 | }() |
| 41 | out, err := io.ReadAll(ptmx) |
| 42 | if err != nil && !strings.Contains(err.Error(), "input/output error") { |
| 43 | t.Fatal("failed to get command output") |
| 44 | } |
| 45 | |
| 46 | // the plugin is attached to the TTY, so the parent process |
| 47 | // ignores the received signal, and the plugin receives a SIGINT |
| 48 | // as well |
| 49 | assert.Equal(t, string(out), "received SIGINT\r\nexit after 3 seconds\r\n") |
| 50 | }) |
| 51 | |
| 52 | // ensure that we don't break plugins that attempt to read from the TTY |
| 53 | // (see: https://github.com/moby/moby/issues/47073) |
| 54 | // (remove me if/when we decide to break compatibility here) |
| 55 | t.Run("the plugin can read from the TTY", func(t *testing.T) { |
| 56 | cmd := run("presocket", "tty") |
| 57 | command := exec.Command(cmd.Command[0], cmd.Command[1:]...) |
| 58 | |
| 59 | ptmx, err := pty.Start(command) |
| 60 | assert.NilError(t, err, "failed to launch command with fake TTY") |
| 61 | _, _ = ptmx.WriteString("hello!") |
| 62 | |
| 63 | done := make(chan error) |
| 64 | go func() { |
| 65 | <-time.After(time.Second) |
| 66 | _, err := io.ReadAll(ptmx) |
| 67 | done <- err |
| 68 | }() |
| 69 | |
| 70 | select { |
| 71 | case cmdErr := <-done: |
| 72 | if cmdErr != nil && !strings.Contains(cmdErr.Error(), "input/output error") { |
| 73 | t.Fatal("failed to get command output") |
| 74 | } |
| 75 | case <-time.After(5 * time.Second): |
| 76 | t.Fatal("timed out! plugin process probably stuck") |
| 77 | } |
| 78 | }) |