mirror of
https://github.com/kata-containers/kata-containers.git
synced 2025-04-29 12:14:48 +00:00
test: Add a unit test for ioCopy()
Following the fix for #1713, adding a unit test for ioCopy() that verifies that data is properly copied from source to destination whatever the order in which the pipes are closed. Fixes #1831 Signed-off-by: Julien Ropé <jrope@redhat.com>
This commit is contained in:
parent
d4a5413774
commit
a918c46fb6
@ -89,3 +89,189 @@ func TestNewTtyIOFifoReopen(t *testing.T) {
|
||||
checkFifoRead(outr)
|
||||
checkFifoRead(errr)
|
||||
}
|
||||
|
||||
func TestIoCopy(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
ctx := context.TODO()
|
||||
|
||||
testBytes1 := []byte("Test1")
|
||||
testBytes2 := []byte("Test2")
|
||||
testBytes3 := []byte("Test3")
|
||||
|
||||
fifoPath, err := ioutil.TempDir(testDir, "fifo-path-")
|
||||
assert.NoError(err)
|
||||
dstStdoutPath := filepath.Join(fifoPath, "dststdout")
|
||||
dstStderrPath := filepath.Join(fifoPath, "dststderr")
|
||||
|
||||
// test function: create pipes, and use ioCopy() to copy data from one set to the other
|
||||
// this function will be called multiple times, testing different combinations of closing order
|
||||
// in order to verify that closing a pipe doesn't break the copy for the others
|
||||
ioCopyTest := func(first, second, third string) {
|
||||
var srcStdinPath string
|
||||
if third != "" {
|
||||
srcStdinPath = filepath.Join(fifoPath, "srcstdin")
|
||||
}
|
||||
|
||||
logErrorMsg := func(msg string) string {
|
||||
return "Error found while using order [" + first + " " + second + " " + third + "] - " + msg
|
||||
}
|
||||
|
||||
exitioch := make(chan struct{})
|
||||
stdinCloser := make(chan struct{})
|
||||
|
||||
createFifo := func(f string) (io.ReadCloser, io.WriteCloser) {
|
||||
reader, err := fifo.OpenFifo(ctx, f, syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
writer, err := fifo.OpenFifo(ctx, f, syscall.O_WRONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700)
|
||||
if err != nil {
|
||||
reader.Close()
|
||||
t.Fatal(err)
|
||||
}
|
||||
return reader, writer
|
||||
}
|
||||
|
||||
// create two sets of stdin, stdout and stderr pipes, to copy data from one to the other
|
||||
srcOutR, srcOutW := createFifo(filepath.Join(fifoPath, "srcstdout"))
|
||||
defer srcOutR.Close()
|
||||
defer srcOutW.Close()
|
||||
|
||||
srcErrR, srcErrW := createFifo(filepath.Join(fifoPath, "srcstderr"))
|
||||
defer srcErrR.Close()
|
||||
defer srcErrW.Close()
|
||||
|
||||
dstInR, dstInW := createFifo(filepath.Join(fifoPath, "dststdin"))
|
||||
defer dstInR.Close()
|
||||
defer dstInW.Close()
|
||||
|
||||
dstOutR, err := fifo.OpenFifo(ctx, dstStdoutPath, syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer dstOutR.Close()
|
||||
dstErrR, err := fifo.OpenFifo(ctx, dstStderrPath, syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer dstErrR.Close()
|
||||
|
||||
var srcInW io.WriteCloser
|
||||
if srcStdinPath != "" {
|
||||
srcInW, err = fifo.OpenFifo(ctx, srcStdinPath, syscall.O_WRONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer srcInW.Close()
|
||||
}
|
||||
|
||||
tty, err := newTtyIO(ctx, srcStdinPath, dstStdoutPath, dstStderrPath, false)
|
||||
assert.NoError(err)
|
||||
defer tty.close()
|
||||
|
||||
// start the ioCopy threads : copy from src to dst
|
||||
go ioCopy(exitioch, stdinCloser, tty, dstInW, srcOutR, srcErrR)
|
||||
|
||||
var firstW, secondW, thirdW io.WriteCloser
|
||||
var firstR, secondR, thirdR io.Reader
|
||||
getPipes := func(order string) (io.Reader, io.WriteCloser) {
|
||||
switch order {
|
||||
case "out":
|
||||
return dstOutR, srcOutW
|
||||
case "err":
|
||||
return dstErrR, srcErrW
|
||||
case "in":
|
||||
return dstInR, srcInW
|
||||
case "":
|
||||
return nil, nil
|
||||
}
|
||||
t.Fatal("internal error")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
firstR, firstW = getPipes(first)
|
||||
secondR, secondW = getPipes(second)
|
||||
thirdR, thirdW = getPipes(third)
|
||||
|
||||
checkFifoWrite := func(w io.Writer, b []byte, name string) {
|
||||
_, err := w.Write(b)
|
||||
if name == "in" && (name == third || name == second && first == "out") {
|
||||
// this is expected: when stdout is closed, ioCopy() will close stdin
|
||||
// so if "in" is after "out", we will get here
|
||||
} else {
|
||||
assert.NoError(err, logErrorMsg("Write error on std"+name))
|
||||
}
|
||||
}
|
||||
checkFifoRead := func(r io.Reader, b []byte, name string) {
|
||||
var err error
|
||||
buf := make([]byte, 5)
|
||||
done := make(chan struct{})
|
||||
timer := time.NewTimer(2 * time.Second)
|
||||
go func() {
|
||||
_, err = r.Read(buf)
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
case <-done:
|
||||
assert.NoError(err, logErrorMsg("Error reading from std"+name))
|
||||
assert.Equal(b, buf, logErrorMsg("Value mismatch on std"+name))
|
||||
case <-timer.C:
|
||||
//t.Fatal(logErrorMsg("read fifo timeout on std" + name))
|
||||
if name == "in" && (name == third || name == second && first == "out") {
|
||||
// this is expected: when stdout is closed, ioCopy() will close stdin
|
||||
// so if "in" is after "out", we will get here
|
||||
} else {
|
||||
assert.Fail(logErrorMsg("read fifo timeout on std" + name))
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// write to each pipe, and close them immediately
|
||||
// the ioCopy function should copy the data, then stop the corresponding thread
|
||||
checkFifoWrite(firstW, testBytes1, first)
|
||||
firstW.Close()
|
||||
|
||||
// need to make sure the Close() above is done before we continue
|
||||
time.Sleep(time.Second)
|
||||
|
||||
checkFifoWrite(secondW, testBytes2, second)
|
||||
secondW.Close()
|
||||
|
||||
if thirdW != nil {
|
||||
// need to make sure the Close() above is done before we continue
|
||||
time.Sleep(time.Second)
|
||||
|
||||
checkFifoWrite(thirdW, testBytes3, third)
|
||||
thirdW.Close()
|
||||
}
|
||||
|
||||
// wait for the end of the ioCopy
|
||||
timer := time.NewTimer(2 * time.Second)
|
||||
select {
|
||||
case <-exitioch:
|
||||
// now check that all data has been copied properly
|
||||
checkFifoRead(firstR, testBytes1, first)
|
||||
checkFifoRead(secondR, testBytes2, second)
|
||||
if thirdR != nil {
|
||||
checkFifoRead(thirdR, testBytes3, third)
|
||||
}
|
||||
case <-timer.C:
|
||||
t.Fatal(logErrorMsg("timeout waiting for ioCopy()"))
|
||||
}
|
||||
}
|
||||
|
||||
// try the different combinations
|
||||
|
||||
// tests without stdin
|
||||
ioCopyTest("out", "err", "")
|
||||
ioCopyTest("err", "out", "")
|
||||
|
||||
// tests with stdin
|
||||
ioCopyTest("out", "err", "in")
|
||||
ioCopyTest("out", "in", "err")
|
||||
ioCopyTest("err", "out", "in")
|
||||
ioCopyTest("err", "in", "out")
|
||||
ioCopyTest("in", "out", "err")
|
||||
ioCopyTest("in", "err", "out")
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user