Increase coverage (#42)

* Introduce mocking framework and increase coverage (#22)

This change introduces two common golang patterns:

- types: this will allow fine-tuned control over imported
types by defining where they will be used and how

- mock: this allows the generation of mock constructors,
which allows for testing any individual path in a method by
"injecting" a mock method which matches the expected type

This change also increases test coverage to 100%

Co-authored-by: Yoshiyuki Mineo <Yoshiyuki.Mineo@jp.sony.com>

* gofmt

---------

Co-authored-by: Bradley Boutcher <btboutcher@icloud.com>
This commit is contained in:
Yoshiyuki MineoandBradley Boutcher authored and GitHub committed 2023-05-04 00:08:20 +09:00
1 parent cc94b60628
commit 597171da2e
4 files changed
+145 -6

No files matched your search

+35
View File
@@ -0,0 +1,35 @@
// Package mock offers implementations of interfaces defined in types.go
// This allows complete control over input / output for any given method that consumes
// a given type
package mock
import (
"fmt"
"net"
"github.com/sony/sonyflake/types"
)
// NewSuccessfulInterfaceAddrs returns a single private IP address
func NewSuccessfulInterfaceAddrs() types.InterfaceAddrs {
ifat := make([]net.Addr, 0, 1)
ifat = append(ifat, &net.IPNet{IP: []byte{192, 168, 0, 1}, Mask: []byte{255, 0, 0, 0}})
return func() ([]net.Addr, error) {
return ifat, nil
}
}
// NewFailingInterfaceAddrs returns an error
func NewFailingInterfaceAddrs() types.InterfaceAddrs {
return func() ([]net.Addr, error) {
return nil, fmt.Errorf("test error")
}
}
// NewFailingInterfaceAddrs returns an empty slice of addresses
func NewNilInterfaceAddrs() types.InterfaceAddrs {
return func() ([]net.Addr, error) {
return []net.Addr{}, nil
}
}
+9 -5
View File
@@ -12,6 +12,8 @@ import (
"net" "net"
"sync" "sync"
"time" "time"
"github.com/sony/sonyflake/types"
) )
// These constants are the bit lengths of Sonyflake ID parts. // These constants are the bit lengths of Sonyflake ID parts.
@@ -50,6 +52,8 @@ type Sonyflake struct {
machineID uint16 machineID uint16
} }
var defaultInterfaceAddrs = net.InterfaceAddrs
// NewSonyflake returns a new Sonyflake configured with the given Settings. // NewSonyflake returns a new Sonyflake configured with the given Settings.
// NewSonyflake returns nil in the following cases: // NewSonyflake returns nil in the following cases:
// - Settings.StartTime is ahead of the current time. // - Settings.StartTime is ahead of the current time.
@@ -71,7 +75,7 @@ func NewSonyflake(st Settings) *Sonyflake {
var err error var err error
if st.MachineID == nil { if st.MachineID == nil {
sf.machineID, err = lower16BitPrivateIP() sf.machineID, err = lower16BitPrivateIP(defaultInterfaceAddrs)
} else { } else {
sf.machineID, err = st.MachineID() sf.machineID, err = st.MachineID()
} }
@@ -131,8 +135,8 @@ func (sf *Sonyflake) toID() (uint64, error) {
uint64(sf.machineID), nil uint64(sf.machineID), nil
} }
func privateIPv4() (net.IP, error) { func privateIPv4(interfaceAddrs types.InterfaceAddrs) (net.IP, error) {
as, err := net.InterfaceAddrs() as, err := interfaceAddrs()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -156,8 +160,8 @@ func isPrivateIPv4(ip net.IP) bool {
(ip[0] == 10 || ip[0] == 172 && (ip[1] >= 16 && ip[1] < 32) || ip[0] == 192 && ip[1] == 168) (ip[0] == 10 || ip[0] == 172 && (ip[1] >= 16 && ip[1] < 32) || ip[0] == 192 && ip[1] == 168)
} }
func lower16BitPrivateIP() (uint16, error) { func lower16BitPrivateIP(interfaceAddrs types.InterfaceAddrs) (uint16, error) {
ip, err := privateIPv4() ip, err := privateIPv4(interfaceAddrs)
if err != nil { if err != nil {
return 0, err return 0, err
} }
+93 -1
View File
@@ -1,10 +1,15 @@
package sonyflake package sonyflake
import ( import (
"bytes"
"fmt" "fmt"
"net"
"runtime" "runtime"
"testing" "testing"
"time" "time"
"github.com/sony/sonyflake/mock"
"github.com/sony/sonyflake/types"
) )
var sf *Sonyflake var sf *Sonyflake
@@ -23,7 +28,7 @@ func init() {
startTime = toSonyflakeTime(st.StartTime) startTime = toSonyflakeTime(st.StartTime)
ip, _ := lower16BitPrivateIP() ip, _ := lower16BitPrivateIP(defaultInterfaceAddrs)
machineID = uint64(ip) machineID = uint64(ip)
} }
@@ -185,6 +190,93 @@ func TestNextIDError(t *testing.T) {
} }
} }
func TestPrivateIPv4(t *testing.T) {
testCases := []struct {
description string
expected net.IP
interfaceAddrs types.InterfaceAddrs
error string
}{
{
description: "InterfaceAddrs returns an error",
expected: nil,
interfaceAddrs: mock.NewFailingInterfaceAddrs(),
error: "test error",
},
{
description: "InterfaceAddrs returns an empty or nil list",
expected: nil,
interfaceAddrs: mock.NewNilInterfaceAddrs(),
error: "no private ip address",
},
{
description: "InterfaceAddrs returns one or more IPs",
expected: net.IP{192, 168, 0, 1},
interfaceAddrs: mock.NewSuccessfulInterfaceAddrs(),
error: "",
},
}
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
actual, err := privateIPv4(tc.interfaceAddrs)
if (err != nil) && (tc.error == "") {
t.Errorf("expected no error, but got: %s", err)
return
} else if (err != nil) && (tc.error != "") {
return
}
if bytes.Equal(actual, tc.expected) {
return
} else {
t.Errorf("error: expected: %s, but got: %s", tc.expected, actual)
}
})
}
}
func TestLower16BitPrivateIP(t *testing.T) {
testCases := []struct {
description string
expected uint16
interfaceAddrs types.InterfaceAddrs
error string
}{
{
description: "InterfaceAddrs returns an empty or nil list",
expected: 0,
interfaceAddrs: mock.NewNilInterfaceAddrs(),
error: "no private ip address",
},
{
description: "InterfaceAddrs returns one or more IPs",
expected: 1,
interfaceAddrs: mock.NewSuccessfulInterfaceAddrs(),
error: "",
},
}
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
actual, err := lower16BitPrivateIP(tc.interfaceAddrs)
if (err != nil) && (tc.error == "") {
t.Errorf("expected no error, but got: %s", err)
return
} else if (err != nil) && (tc.error != "") {
return
}
if actual == tc.expected {
return
} else {
t.Errorf("error: expected: %v, but got: %v", tc.expected, actual)
}
})
}
}
func TestSonyflakeTimeUnit(t *testing.T) { func TestSonyflakeTimeUnit(t *testing.T) {
if time.Duration(sonyflakeTimeUnit) != 10*time.Millisecond { if time.Duration(sonyflakeTimeUnit) != 10*time.Millisecond {
t.Errorf("unexpected time unit") t.Errorf("unexpected time unit")
+8
View File
@@ -0,0 +1,8 @@
// Package Types defines type signatures used throughout SonyFlake. This allows for
// fine-tuned control over imports, and the ability to mock out imports as well
package types
import "net"
// InterfaceAddrs defines the interface used for retrieving network addresses
type InterfaceAddrs func() ([]net.Addr, error)