Skip to content

Commit

Permalink
Add tests for IPMI package
Browse files Browse the repository at this point in the history
Signed-off-by: Christopher Meis <christopher.meis@9elements.com>
  • Loading branch information
ChriMarMe committed Dec 8, 2021
1 parent c9f2462 commit 4b73aba
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 128 deletions.
31 changes: 5 additions & 26 deletions pkg/ipmi/dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,19 @@ import (
"log"
"os"
"runtime"
"syscall"
"time"
"unsafe"

"golang.org/x/sys/unix"
)

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())
Expand All @@ -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)
Expand Down Expand Up @@ -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{},
}
}
214 changes: 124 additions & 90 deletions pkg/ipmi/dev_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/ipmi/ipmi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
11 changes: 0 additions & 11 deletions pkg/ipmi/pkg_test.go

This file was deleted.

0 comments on commit 4b73aba

Please sign in to comment.