Skip to content

Commit

Permalink
Merge pull request #3561 from apostasie/remove-unbuffer
Browse files Browse the repository at this point in the history
Add pseudo tty support for tests
  • Loading branch information
AkihiroSuda authored Oct 17, 2024
2 parents 506d875 + 97919c2 commit b27b544
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 12 deletions.
20 changes: 8 additions & 12 deletions cmd/nerdctl/issues/main_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,30 +33,26 @@ func TestMain(m *testing.M) {
func TestIssue108(t *testing.T) {
testCase := nerdtest.Setup()

testCase.Require = test.Linux

testCase.SubTests = []*test.Case{
{
Description: "-it --net=host",
Require: test.Binary("unbuffer"),
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
cmd := helpers.
Command("run", "-it", "--rm", "--net=host", testutil.AlpineImage, "echo", "this was always working")
cmd.WithWrapper("unbuffer")
cmd := helpers.Command("run", "-it", "--rm", "--net=host", testutil.AlpineImage, "echo", "this was always working")
cmd.WithPseudoTTY()
return cmd
},
// Note: unbuffer will merge stdout and stderr, preventing exact match here
Expected: test.Expects(0, nil, test.Contains("this was always working")),
Expected: test.Expects(0, nil, test.Equals("this was always working\r\n")),
},
{
Description: "--net=host -it",
Require: test.Binary("unbuffer"),
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
cmd := helpers.
Command("run", "--rm", "--net=host", "-it", testutil.AlpineImage, "echo", "this was not working due to issue #108")
cmd.WithWrapper("unbuffer")
cmd := helpers.Command("run", "--rm", "--net=host", "-it", testutil.AlpineImage, "echo", "this was not working due to issue #108")
cmd.WithPseudoTTY()
return cmd
},
// Note: unbuffer will merge stdout and stderr, preventing exact match here
Expected: test.Expects(0, nil, test.Contains("this was not working due to issue #108")),
Expected: test.Expects(0, nil, test.Equals("this was not working due to issue #108\r\n")),
},
}

Expand Down
16 changes: 16 additions & 0 deletions pkg/testutil/test/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type GenericCommand struct {
envBlackList []string
stdin io.Reader
async bool
pty bool
timeout time.Duration
workingDir string

Expand All @@ -65,6 +66,10 @@ func (gc *GenericCommand) WithWrapper(binary string, args ...string) {
gc.helperArgs = args
}

func (gc *GenericCommand) WithPseudoTTY() {
gc.pty = true
}

func (gc *GenericCommand) WithStdin(r io.Reader) {
gc.stdin = r
}
Expand All @@ -77,6 +82,7 @@ func (gc *GenericCommand) WithCwd(path string) {
// Primitives (gc.timeout) is here, it is just a matter of exposing a WithTimeout method
// - UX to be decided
// - validate use case: would we ever need this?

func (gc *GenericCommand) Run(expect *Expected) {
if gc.t != nil {
gc.t.Helper()
Expand All @@ -90,6 +96,16 @@ func (gc *GenericCommand) Run(expect *Expected) {
} else {
iCmdCmd := gc.boot()
env = iCmdCmd.Env

if gc.pty {
pty, tty, _ := Open()
iCmdCmd.Stdin = tty
iCmdCmd.Stdout = tty
iCmdCmd.Stderr = tty
defer pty.Close()
defer tty.Close()
}

// Run it
result = icmd.RunCmd(iCmdCmd)
}
Expand Down
22 changes: 22 additions & 0 deletions pkg/testutil/test/pty.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package test

import "errors"

var ErrPTYFailure = errors.New("pty failure")
var ErrPTYUnsupportedPlatform = errors.New("pty not supported on this platform")
25 changes: 25 additions & 0 deletions pkg/testutil/test/pty_freebsd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package test

import (
"os"
)

func Open() (pty, tty *os.File, err error) {
return nil, nil, ErrPTYUnsupportedPlatform
}
72 changes: 72 additions & 0 deletions pkg/testutil/test/pty_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package test

import (
"errors"
"os"
"strconv"
"syscall"
"unsafe"
)

// Inspiration from https://github.com/creack/pty/tree/2cde18bfb702199728dd43bf10a6c15c7336da0a

func Open() (pty, tty *os.File, err error) {
defer func() {
if err != nil && pty != nil {
err = errors.Join(pty.Close(), err)
}
if err != nil {
err = errors.Join(ErrPTYFailure, err)
}
}()

pty, err = os.OpenFile("/dev/ptmx", os.O_RDWR, 0)
if err != nil {
return nil, nil, err
}

var n uint32
err = ioctl(pty, syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n)))
if err != nil {
return nil, nil, err
}

sname := "/dev/pts/" + strconv.Itoa(int(n))

var u int32
err = ioctl(pty, syscall.TIOCSPTLCK, uintptr(unsafe.Pointer(&u)))
if err != nil {
return nil, nil, err
}

tty, err = os.OpenFile(sname, os.O_RDWR|syscall.O_NOCTTY, 0)
if err != nil {
return nil, nil, err
}

return pty, tty, nil
}

func ioctl(f *os.File, cmd, ptr uintptr) error {
_, _, e := syscall.Syscall(syscall.SYS_IOCTL, f.Fd(), cmd, ptr)
if e != 0 {
return e
}
return nil
}
25 changes: 25 additions & 0 deletions pkg/testutil/test/pty_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package test

import (
"os"
)

func Open() (pty, tty *os.File, err error) {
return nil, nil, ErrPTYUnsupportedPlatform
}
2 changes: 2 additions & 0 deletions pkg/testutil/test/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ type TestableCommand interface {
WithArgs(args ...string)
// WithWrapper allows wrapping a command with another command (for example: `time`, `unbuffer`)
WithWrapper(binary string, args ...string)
// WithPseudoTTY
WithPseudoTTY()
// WithStdin allows passing a reader to be used for stdin for the command
WithStdin(r io.Reader)
// WithCwd allows specifying the working directory for the command
Expand Down

0 comments on commit b27b544

Please sign in to comment.