From 597171da2edf819fbaf5932b485ae4f0fa642f0e Mon Sep 17 00:00:00 2001 From: Yoshiyuki Mineo Date: Thu, 4 May 2023 00:08:20 +0900 Subject: [PATCH 1/3] 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 * gofmt --------- Co-authored-by: Bradley Boutcher --- mock/sonyflake_mock.go | 35 ++++++++++++++++ sonyflake.go | 14 ++++--- sonyflake_test.go | 94 +++++++++++++++++++++++++++++++++++++++++- types/types.go | 8 ++++ 4 files changed, 145 insertions(+), 6 deletions(-) create mode 100644 mock/sonyflake_mock.go create mode 100644 types/types.go diff --git a/mock/sonyflake_mock.go b/mock/sonyflake_mock.go new file mode 100644 index 0000000..70bc01a --- /dev/null +++ b/mock/sonyflake_mock.go @@ -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 + } +} diff --git a/sonyflake.go b/sonyflake.go index ebafa46..1c1402e 100644 --- a/sonyflake.go +++ b/sonyflake.go @@ -12,6 +12,8 @@ import ( "net" "sync" "time" + + "github.com/sony/sonyflake/types" ) // These constants are the bit lengths of Sonyflake ID parts. @@ -50,6 +52,8 @@ type Sonyflake struct { machineID uint16 } +var defaultInterfaceAddrs = net.InterfaceAddrs + // NewSonyflake returns a new Sonyflake configured with the given Settings. // NewSonyflake returns nil in the following cases: // - Settings.StartTime is ahead of the current time. @@ -71,7 +75,7 @@ func NewSonyflake(st Settings) *Sonyflake { var err error if st.MachineID == nil { - sf.machineID, err = lower16BitPrivateIP() + sf.machineID, err = lower16BitPrivateIP(defaultInterfaceAddrs) } else { sf.machineID, err = st.MachineID() } @@ -131,8 +135,8 @@ func (sf *Sonyflake) toID() (uint64, error) { uint64(sf.machineID), nil } -func privateIPv4() (net.IP, error) { - as, err := net.InterfaceAddrs() +func privateIPv4(interfaceAddrs types.InterfaceAddrs) (net.IP, error) { + as, err := interfaceAddrs() if err != nil { 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) } -func lower16BitPrivateIP() (uint16, error) { - ip, err := privateIPv4() +func lower16BitPrivateIP(interfaceAddrs types.InterfaceAddrs) (uint16, error) { + ip, err := privateIPv4(interfaceAddrs) if err != nil { return 0, err } diff --git a/sonyflake_test.go b/sonyflake_test.go index 8c4999f..db58778 100644 --- a/sonyflake_test.go +++ b/sonyflake_test.go @@ -1,10 +1,15 @@ package sonyflake import ( + "bytes" "fmt" + "net" "runtime" "testing" "time" + + "github.com/sony/sonyflake/mock" + "github.com/sony/sonyflake/types" ) var sf *Sonyflake @@ -23,7 +28,7 @@ func init() { startTime = toSonyflakeTime(st.StartTime) - ip, _ := lower16BitPrivateIP() + ip, _ := lower16BitPrivateIP(defaultInterfaceAddrs) 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) { if time.Duration(sonyflakeTimeUnit) != 10*time.Millisecond { t.Errorf("unexpected time unit") diff --git a/types/types.go b/types/types.go new file mode 100644 index 0000000..57fa350 --- /dev/null +++ b/types/types.go @@ -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) From eafab81cd5dd186fcc7806902fd86c104225fd5a Mon Sep 17 00:00:00 2001 From: Yoshiyuki Mineo Date: Thu, 4 May 2023 02:08:54 +0900 Subject: [PATCH 2/3] Update go versions (#43) --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cc50b4b..cb5013f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,7 +4,7 @@ jobs: test: strategy: matrix: - go-version: [1.18.x, 1.19.x] + go-version: [1.19.x, 1.20.x] os: [ubuntu-latest] runs-on: ${{matrix.os}} steps: From 18c490832118139d0811798c91fa554b7ac9a7a7 Mon Sep 17 00:00:00 2001 From: Yoshiyuki Mineo Date: Sun, 13 Aug 2023 22:53:30 +0900 Subject: [PATCH 3/3] Update go versions --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cb5013f..c281d82 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,7 +4,7 @@ jobs: test: strategy: matrix: - go-version: [1.19.x, 1.20.x] + go-version: [1.20.x, 1.21.x] os: [ubuntu-latest] runs-on: ${{matrix.os}} steps: