From 4b73aba0b7623131e841f8b144cb9f583cb8d81c Mon Sep 17 00:00:00 2001 From: Christopher Meis Date: Wed, 17 Nov 2021 10:52:46 +0100 Subject: [PATCH] Add tests for IPMI package Signed-off-by: Christopher Meis --- pkg/ipmi/dev.go | 31 +----- pkg/ipmi/dev_test.go | 214 ++++++++++++++++++++++++------------------ pkg/ipmi/ipmi_test.go | 2 +- pkg/ipmi/pkg_test.go | 11 --- 4 files changed, 130 insertions(+), 128 deletions(-) delete mode 100644 pkg/ipmi/pkg_test.go diff --git a/pkg/ipmi/dev.go b/pkg/ipmi/dev.go index 6fb8aecae8..4f55fe7ef2 100644 --- a/pkg/ipmi/dev.go +++ b/pkg/ipmi/dev.go @@ -9,8 +9,6 @@ import ( "log" "os" "runtime" - "syscall" - "time" "unsafe" "golang.org/x/sys/unix" @@ -18,22 +16,12 @@ import ( type Dev struct { *os.File - // unixSyscall gives the option to overwrite the actual sysCall with a dummy function when writing tests. - unixSyscall func(uintptr, uintptr, uintptr, uintptr) (uintptr, uintptr, unix.Errno) - - // fileSyscallConn gives the option to overwrite the actual function call with a dummy function when writing tests. - fileSyscallConn func(f *os.File) (syscall.RawConn, error) - - // fileSetReadDeadline gives the option to overwrite the actual function call with a dummy function when writing tests. - fileSetReadDeadline func(f *os.File, t time.Duration) error - - // connRead gives the option to overwrite the actual function call with a dummy function when writing tests. - connRead func(f func(fd uintptr) bool, conn syscall.RawConn) error + syscalls } // SendRequest uses unix.Syscall IOCTL to send a request to the BMC. func (d *Dev) SendRequest(req *request) error { - _, _, err := d.unixSyscall(unix.SYS_IOCTL, d.File.Fd(), _IPMICTL_SEND_COMMAND, uintptr(unsafe.Pointer(req))) + _, _, err := d.syscall(unix.SYS_IOCTL, d.File.Fd(), _IPMICTL_SEND_COMMAND, uintptr(unsafe.Pointer(req))) runtime.KeepAlive(req) if err != 0 { return fmt.Errorf("syscall failed with: %v", err.Error()) @@ -47,7 +35,7 @@ func (d *Dev) ReceiveResponse(msgID int64, resp *response, buf []byte) ([]byte, var result []byte var rerr error readMsg := func(fd uintptr) bool { - _, _, err := d.unixSyscall(unix.SYS_IOCTL, d.File.Fd(), _IPMICTL_RECEIVE_MSG_TRUNC, uintptr(unsafe.Pointer(resp))) + _, _, err := d.syscall(unix.SYS_IOCTL, d.File.Fd(), _IPMICTL_RECEIVE_MSG_TRUNC, uintptr(unsafe.Pointer(resp))) runtime.KeepAlive(resp) if err != 0 { rerr = fmt.Errorf("ioctlGetRecv failed with %v", err) @@ -93,16 +81,7 @@ func (d *Dev) GetFile() *os.File { // GetDev takes a file and returns a new Dev func GetDev(f *os.File) *Dev { return &Dev{ - File: f, - unixSyscall: unix.Syscall, - fileSyscallConn: func(f *os.File) (syscall.RawConn, error) { - return f.SyscallConn() - }, - fileSetReadDeadline: func(f *os.File, t time.Duration) error { - return f.SetReadDeadline(time.Now().Add(t)) - }, - connRead: func(f func(fd uintptr) bool, conn syscall.RawConn) error { - return conn.Read(f) - }, + File: f, + syscalls: &realSyscalls{}, } } diff --git a/pkg/ipmi/dev_test.go b/pkg/ipmi/dev_test.go index bc34158280..650675d4e6 100644 --- a/pkg/ipmi/dev_test.go +++ b/pkg/ipmi/dev_test.go @@ -5,131 +5,165 @@ package ipmi import ( + "fmt" "io/ioutil" "os" + "strings" "syscall" "testing" "time" -) -func TestSendRequestSuccess(t *testing.T) { - df, err := ioutil.TempFile("", "ipmi_dummy_file-") - if err != nil { - t.Error(err) - } - defer os.RemoveAll(df.Name()) - // We need a dummy dev struct - d := Dev{ - File: df, - } - req := &request{} + "golang.org/x/sys/unix" +) - d.unixSyscall = func(trap, a1, a2, a3 uintptr) (uintptr, uintptr, syscall.Errno) { - return 0, 0, 0 - } - if err := d.SendRequest(req); err != nil { - t.Error(err) - } +type testsyscalls struct { + forceErrno unix.Errno + forceConnReadErr error + forcesetReadDeadlineErr error + forceConnErr error } -func TestSendRequestFailWithSyscall(t *testing.T) { - df, err := ioutil.TempFile("", "ipmi_dummy_file-") - if err != nil { - t.Error(err) - } - defer os.RemoveAll(df.Name()) - // We need a dummy dev struct - d := Dev{ - File: df, +func (t *testsyscalls) syscall(trap, a1, a2, a3 uintptr) (uintptr, uintptr, unix.Errno) { + if t.forceErrno != 0 { + return 0, 0, t.forceErrno } - req := &request{} - d.unixSyscall = func(trap, a1, a2, a3 uintptr) (uintptr, uintptr, syscall.Errno) { - return 0, 0, 1 - } - if err := d.SendRequest(req); err == nil { - t.Error("Test failed because function succeeded") + if trap != unix.SYS_IOCTL { + return 0, 0, unix.EINVAL } -} -func TestReceiveResponseSuccess(t *testing.T) { - df, err := ioutil.TempFile("", "ipmi_dummy_file-") - if err != nil { - t.Error(err) - } - defer os.RemoveAll(df.Name()) - // We need a dummy dev struct - d := GetDev(df) - - resp := &response{} - buf := make([]byte, 1) - d.unixSyscall = func(trap, a1, a2, a3 uintptr) (uintptr, uintptr, syscall.Errno) { - return 0, 0, 0 + if a1 < 1 { + return 0, 0, unix.EINVAL } - d.fileSetReadDeadline = func(f *os.File, t time.Duration) error { - return nil + if a3 < 1 { + return 0, 0, unix.EINVAL } - _, err = d.ReceiveResponse(0, resp, buf) - if err != nil { - t.Error(err) + switch a2 { + case _IPMICTL_RECEIVE_MSG_TRUNC, _IPMICTL_SEND_COMMAND: + return 0, 0, 0 + default: + return 0, 0, unix.EINVAL } } -func TestReceiveResponseFailWithMsgID(t *testing.T) { - df, err := ioutil.TempFile("", "ipmi_dummy_file-") - if err != nil { - t.Error(err) - } - defer os.RemoveAll(df.Name()) - // We need a dummy dev struct - d := GetDev(df) - - // Message ID is set to 1, but will be expected to be zero later. - resp := &response{ - msgid: 1, - } - buf := make([]byte, 1) - d.unixSyscall = func(trap, a1, a2, a3 uintptr) (uintptr, uintptr, syscall.Errno) { - return 0, 0, 0 +func (t *testsyscalls) fileSyscallConn(f *os.File) (syscall.RawConn, error) { + if t.forceConnErr != nil { + return nil, t.forceConnErr } + return f.SyscallConn() +} - d.fileSetReadDeadline = func(f *os.File, t time.Duration) error { - return nil +// This function only need to return nil. The real deal only works on special file descriptors. +func (t *testsyscalls) fileSetReadDeadline(f *os.File, time time.Duration) error { + if t.forcesetReadDeadlineErr != nil { + return t.forcesetReadDeadlineErr } + return nil +} - // We expect 0 as message ID, but want the message for one. This lets the function return the error - _, err = d.ReceiveResponse(0, resp, buf) - if err == nil { - t.Error("function ReceiveResponse() succeeded but should have failed") +func (t *testsyscalls) connRead(f func(fd uintptr) bool, conn syscall.RawConn) error { + if t.forceConnReadErr != nil { + return t.forceConnReadErr } + return conn.Read(f) } -func TestReceiveResponseFailWithSyscall(t *testing.T) { +func TestDev(t *testing.T) { df, err := ioutil.TempFile("", "ipmi_dummy_file-") if err != nil { t.Error(err) } - defer os.RemoveAll(df.Name()) - // We need a dummy dev struct - d := GetDev(df) - resp := &response{ - msgid: 0, - } - buf := make([]byte, 1) - d.unixSyscall = func(trap, a1, a2, a3 uintptr) (uintptr, uintptr, syscall.Errno) { - return 0, 0, 1 - } + sc := &testsyscalls{} - d.fileSetReadDeadline = func(f *os.File, t time.Duration) error { - return nil + d := Dev{ + File: df, + syscalls: sc, } + defer os.RemoveAll(df.Name()) + + for _, tt := range []struct { + name string + req *request + resp *response + forceErrno unix.Errno + wantSendErr error + wantRecvErr error + connReadErr error + deadlineErr error + }{ + { + name: "NoError", + req: &request{}, + resp: &response{}, + }, + { + name: "ForceSysCallError", + forceErrno: unix.Errno(1), + wantSendErr: fmt.Errorf("syscall failed with: operation not permitted"), + wantRecvErr: fmt.Errorf("failed to read rawconn"), + req: &request{}, + resp: &response{}, + }, + { + name: "ForceConnError", + req: &request{}, + resp: &response{}, + wantRecvErr: fmt.Errorf("failed to get file rawconn"), + connReadErr: fmt.Errorf("Force connRead error"), + }, + { + name: "ForceSetReadDeadlineError", + req: &request{}, + resp: &response{}, + wantRecvErr: fmt.Errorf("failed to set read deadline"), + deadlineErr: fmt.Errorf("force set read deadline"), + }, + { + name: "FailMessageID", + req: &request{ + msgid: 1, + }, + resp: &response{ + msgid: 2, + }, + wantRecvErr: fmt.Errorf("received wrong message"), + }, + { + name: "ForceLongRecvMsgDataLen", + req: &request{}, + resp: &response{ + msg: Msg{ + DataLen: _IPMI_BUF_SIZE + 1, + }, + }, + wantRecvErr: fmt.Errorf("data length received too large"), + }, + } { + sc.forceErrno = tt.forceErrno + sc.forceConnReadErr = tt.connReadErr + sc.forceConnErr = tt.connReadErr + sc.forcesetReadDeadlineErr = tt.deadlineErr + + t.Run("SendRequest"+tt.name, func(t *testing.T) { + if err := d.SendRequest(tt.req); err != nil { + if !strings.Contains(err.Error(), tt.wantSendErr.Error()) { + t.Errorf("Test %q failed. Want: %q, Got: %q", tt.name, tt.wantSendErr, err) + } + } + }) + + t.Run(tt.name, func(t *testing.T) { + buf := make([]byte, 1) + if _, err := d.ReceiveResponse(0, tt.resp, buf); err != nil { + if !strings.Contains(err.Error(), tt.wantRecvErr.Error()) { + t.Errorf("Test %q failed. Want: %q, Got: %q", tt.name, tt.wantRecvErr, err) + } + } + }) - _, err = d.ReceiveResponse(0, resp, buf) - if err == nil { - t.Error("function ReceiveResponse() succeeded but should have failed") } } diff --git a/pkg/ipmi/ipmi_test.go b/pkg/ipmi/ipmi_test.go index 2ab4e0141c..7ecf82b531 100644 --- a/pkg/ipmi/ipmi_test.go +++ b/pkg/ipmi/ipmi_test.go @@ -17,7 +17,7 @@ func TestWatchdogRunning(t *testing.T) { } -func TestShutoffWatchdog(t *testing.T) { +func TestShutiffWatchdog(t *testing.T) { i := GetMockIPMI() if err := i.ShutoffWatchdog(); err != nil { t.Error(err) diff --git a/pkg/ipmi/pkg_test.go b/pkg/ipmi/pkg_test.go deleted file mode 100644 index cb4afa0bdf..0000000000 --- a/pkg/ipmi/pkg_test.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2021 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package ipmi - -import "testing" - -func TestTODO(t *testing.T) { - // TODO: Write a unit test. -}