Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions v2/machine_bits_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package sonyflake

import (
"net"
"testing"

"github.com/sony/sonyflake/v2/mock"
)

// TestDefaultMachineIDWithCustomBits tests that when using default machine ID
// with custom BitsMachineID, the machine ID is properly masked to fit within
// the specified bit length.
func TestDefaultMachineIDWithCustomBits(t *testing.T) {
testCases := []struct {
name string
bitsMachineID int
mockIP net.IP
expectError bool
}{
{
name: "10 bits machine ID with IP that fits",
bitsMachineID: 10,
mockIP: net.IP{192, 168, 0, 1}, // lower 16 bits = 1, fits in 10 bits
expectError: false,
},
{
name: "10 bits machine ID with IP that exceeds without masking",
bitsMachineID: 10,
mockIP: net.IP{192, 168, 255, 255}, // lower 16 bits = 65535, needs masking to fit in 10 bits
expectError: false,
},
{
name: "8 bits machine ID",
bitsMachineID: 8,
mockIP: net.IP{192, 168, 100, 200}, // lower 16 bits = 25800
expectError: false,
},
{
name: "default 16 bits",
bitsMachineID: 0, // will use default 16
mockIP: net.IP{192, 168, 255, 255},
expectError: false,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Create a mock that returns our test IP
mockInterfaceAddrs := mock.NewInterfaceAddrsWithIP(tc.mockIP)

settings := Settings{
BitsMachineID: tc.bitsMachineID,
}

// Temporarily replace the default interface addrs function
oldDefaultInterfaceAddrs := defaultInterfaceAddrs
defaultInterfaceAddrs = mockInterfaceAddrs
defer func() { defaultInterfaceAddrs = oldDefaultInterfaceAddrs }()

sf, err := New(settings)

if tc.expectError {
if err == nil {
t.Errorf("expected error but got none")
}
} else {
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if sf == nil {

Check failure on line 70 in v2/machine_bits_test.go

View workflow job for this annotation

GitHub Actions / test-v2 (1.24.x, ubuntu-latest)

SA5011(related information): this check suggests that the pointer can be nil (staticcheck)

Check failure on line 70 in v2/machine_bits_test.go

View workflow job for this annotation

GitHub Actions / test-v2 (1.23.x, ubuntu-latest)

SA5011(related information): this check suggests that the pointer can be nil (staticcheck)

Check failure on line 70 in v2/machine_bits_test.go

View workflow job for this annotation

GitHub Actions / test-v2 (1.24.x, ubuntu-latest)

SA5011(related information): this check suggests that the pointer can be nil (staticcheck)

Check failure on line 70 in v2/machine_bits_test.go

View workflow job for this annotation

GitHub Actions / test-v2 (1.23.x, ubuntu-latest)

SA5011(related information): this check suggests that the pointer can be nil (staticcheck)
t.Error("sonyflake instance should not be nil")
}

// Verify the machine ID fits within the specified bits
expectedBits := tc.bitsMachineID
if expectedBits == 0 {
expectedBits = defaultBitsMachine
}
maxMachineID := 1 << expectedBits
if sf.machine >= maxMachineID {

Check failure on line 80 in v2/machine_bits_test.go

View workflow job for this annotation

GitHub Actions / test-v2 (1.24.x, ubuntu-latest)

SA5011: possible nil pointer dereference (staticcheck)

Check failure on line 80 in v2/machine_bits_test.go

View workflow job for this annotation

GitHub Actions / test-v2 (1.23.x, ubuntu-latest)

SA5011: possible nil pointer dereference (staticcheck)

Check failure on line 80 in v2/machine_bits_test.go

View workflow job for this annotation

GitHub Actions / test-v2 (1.24.x, ubuntu-latest)

SA5011: possible nil pointer dereference (staticcheck)

Check failure on line 80 in v2/machine_bits_test.go

View workflow job for this annotation

GitHub Actions / test-v2 (1.23.x, ubuntu-latest)

SA5011: possible nil pointer dereference (staticcheck)
t.Errorf("machine ID %d exceeds max for %d bits (%d)", sf.machine, expectedBits, maxMachineID)
}
}
})
}
}
10 changes: 10 additions & 0 deletions v2/mock/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,13 @@ func NewNilInterfaceAddrs() types.InterfaceAddrs {
return []net.Addr{}, nil
}
}

// NewInterfaceAddrsWithIP returns a private IP address with the given IP.
func NewInterfaceAddrsWithIP(ip net.IP) types.InterfaceAddrs {
ifat := make([]net.Addr, 0, 1)
ifat = append(ifat, &net.IPNet{IP: ip, Mask: []byte{255, 0, 0, 0}})

return func() ([]net.Addr, error) {
return ifat, nil
}
}
7 changes: 6 additions & 1 deletion v2/sonyflake.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ import (
//
// MachineID returns the unique ID of a Sonyflake instance.
// If MachineID returns an error, the instance will not be created.
// If MachineID is nil, the default MachineID is used, which returns the lower 16 bits of the private IP address.
// If MachineID is nil, the default MachineID is used, which returns the lower bits
// of the private IP address, masked to fit within BitsMachineID bits.
//
// CheckMachineID validates the uniqueness of a machine ID.
// If CheckMachineID returns false, the instance will not be created.
Expand Down Expand Up @@ -154,6 +155,10 @@ func New(st Settings) (*Sonyflake, error) {
var err error
if st.MachineID == nil {
sf.machine, err = lower16BitPrivateIP(defaultInterfaceAddrs)
if err == nil {
// Mask to use only the required number of bits
sf.machine = sf.machine & (1<<sf.bitsMachine - 1)
}
} else {
sf.machine, err = st.MachineID()
}
Expand Down
Loading