package sockaddr_test

import (
	"fmt"
	"net"
	"reflect"
	"testing"

	sockaddr "github.com/hashicorp/go-sockaddr"
)

const (
	// NOTE(seanc@): Assume "en0" is the interface with a default route attached
	// to it.  When this is not the case, change this one constant and tests
	// should pass (i.e. "net0").
	ifNameWithDefault = "en0"
)

// NOTE: A number of these code paths are exercised in template/ and
// cmd/sockaddr/.
//
// TODO(sean@): Add better coverage for filtering functions (e.g. ExcludeBy*,
// IncludeBy*).

func TestCmpIfAddrFunc(t *testing.T) {
	tests := []struct {
		name       string
		t1         sockaddr.IfAddr // must come before t2 according to the ascOp
		t2         sockaddr.IfAddr
		ascOp      sockaddr.CmpIfAddrFunc
		ascResult  int
		descOp     sockaddr.CmpIfAddrFunc
		descResult int
	}{
		{
			name:       "empty test",
			t1:         sockaddr.IfAddr{},
			t2:         sockaddr.IfAddr{},
			ascOp:      sockaddr.AscIfAddress,
			descOp:     sockaddr.DescIfAddress,
			ascResult:  0,
			descResult: 0,
		},
		{
			name: "ipv4 address less",
			t1: sockaddr.IfAddr{
				SockAddr: sockaddr.MustIPv4Addr("1.2.3.3"),
			},
			t2: sockaddr.IfAddr{
				SockAddr: sockaddr.MustIPv4Addr("1.2.3.4"),
			},
			ascOp:      sockaddr.AscIfAddress,
			descOp:     sockaddr.DescIfAddress,
			ascResult:  -1,
			descResult: -1,
		},
		{
			name: "ipv4 private",
			t1: sockaddr.IfAddr{
				SockAddr: sockaddr.MustIPv4Addr("10.1.2.3"),
			},
			t2: sockaddr.IfAddr{
				SockAddr: sockaddr.MustIPv4Addr("203.0.113.3"),
			},
			ascOp:      sockaddr.AscIfPrivate,
			descOp:     sockaddr.DescIfPrivate,
			ascResult:  0, // not both private, can't complete the test
			descResult: 0,
		},
		{
			name: "IfAddr name",
			t1: sockaddr.IfAddr{
				SockAddr: sockaddr.MustIPv4Addr("10.1.2.3"),
				Interface: net.Interface{
					Name: "abc0",
				},
			},
			t2: sockaddr.IfAddr{
				SockAddr: sockaddr.MustIPv4Addr("203.0.113.3"),
				Interface: net.Interface{
					Name: "xyz0",
				},
			},
			ascOp:      sockaddr.AscIfName,
			descOp:     sockaddr.DescIfName,
			ascResult:  -1,
			descResult: -1,
		},
		{
			name: "IfAddr network size",
			t1: sockaddr.IfAddr{
				SockAddr: sockaddr.MustIPv4Addr("10.0.0.0/8"),
			},
			t2: sockaddr.IfAddr{
				SockAddr: sockaddr.MustIPv4Addr("127.0.0.0/24"),
			},
			ascOp:      sockaddr.AscIfNetworkSize,
			descOp:     sockaddr.DescIfNetworkSize,
			ascResult:  -1,
			descResult: -1,
		},
		{
			name: "IfAddr port",
			t1: sockaddr.IfAddr{
				SockAddr: sockaddr.MustIPv4Addr("10.0.0.0:80"),
			},
			t2: sockaddr.IfAddr{
				SockAddr: sockaddr.MustIPv4Addr("127.0.0.0:8600"),
			},
			ascOp:      sockaddr.AscIfPort,
			descOp:     sockaddr.DescIfPort,
			ascResult:  -1,
			descResult: -1,
		},
		{
			name: "IfAddr type",
			t1: sockaddr.IfAddr{
				SockAddr: sockaddr.MustIPv4Addr("10.0.0.0:80"),
			},
			t2: sockaddr.IfAddr{
				SockAddr: sockaddr.MustIPv6Addr("[::1]:80"),
			},
			ascOp:      sockaddr.AscIfType,
			descOp:     sockaddr.DescIfType,
			ascResult:  -1,
			descResult: -1,
		},
	}

	for i, test := range tests {
		if test.name == "" {
			t.Fatalf("test %d must have a name", i)
		}

		// Test ascending operation
		ascExpected := test.ascResult
		ascResult := test.ascOp(&test.t1, &test.t2)
		if ascResult != ascExpected {
			t.Errorf("%s: Unexpected result %d, expected %d when comparing %v and %v using %v", test.name, ascResult, ascExpected, test.t1, test.t2, test.ascOp)
		}

		// Test descending operation
		descExpected := test.descResult
		descResult := test.descOp(&test.t2, &test.t1)
		if descResult != descExpected {
			t.Errorf("%s: Unexpected result %d, expected %d when comparing %v and %v using %v", test.name, descResult, descExpected, test.t1, test.t2, test.descOp)
		}

		if ascResult != descResult {
			t.Fatalf("bad")
		}

		// Reverse the args
		ascExpected = -1 * test.ascResult
		ascResult = test.ascOp(&test.t2, &test.t1)
		if ascResult != ascExpected {
			t.Errorf("%s: Unexpected result %d, expected %d when comparing %v and %v using %v", test.name, ascResult, ascExpected, test.t1, test.t2, test.ascOp)
		}

		descExpected = -1 * test.descResult
		descResult = test.descOp(&test.t1, &test.t2)
		if descResult != descExpected {
			t.Errorf("%s: Unexpected result %d, expected %d when comparing %v and %v using %v", test.name, descResult, descExpected, test.t1, test.t2, test.descOp)
		}

		if ascResult != descResult {
			t.Fatalf("bad")
		}

		// Test equality
		ascExpected = 0
		ascResult = test.ascOp(&test.t1, &test.t1)
		if ascResult != ascExpected {
			t.Errorf("%s: Unexpected result %d, expected %d when comparing %v and %v using %v", test.name, ascResult, ascExpected, test.t1, test.t2, test.ascOp)
		}

		descExpected = 0
		descResult = test.descOp(&test.t1, &test.t1)
		if descResult != descExpected {
			t.Errorf("%s: Unexpected result %d, expected %d when comparing %v and %v using %v", test.name, descResult, descExpected, test.t1, test.t2, test.descOp)
		}
	}
}

func TestFilterIfByFlags(t *testing.T) {
	tests := []struct {
		name     string
		selector string
		ifAddrs  sockaddr.IfAddrs
		flags    net.Flags
		fail     bool
	}{
		{
			name:     "broadcast",
			selector: "broadcast",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					Interface: net.Interface{
						Flags: net.FlagBroadcast,
					},
					SockAddr: sockaddr.MustIPv4Addr("1.2.3.1"),
				},
			},
		},
		{
			name:     "down",
			selector: "down",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					Interface: net.Interface{},
					SockAddr:  sockaddr.MustIPv4Addr("1.2.3.2"),
				},
			},
		},
		{
			name:     "forwardable IPv4",
			selector: "forwardable",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					Interface: net.Interface{},
					SockAddr:  sockaddr.MustIPv4Addr("1.2.3.3"),
				},
			},
		},
		{
			name:     "forwardable IPv6",
			selector: "forwardable",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					Interface: net.Interface{},
					SockAddr:  sockaddr.MustIPv6Addr("cc::1/128"),
				},
			},
		},
		{
			name:     "global unicast",
			selector: "global unicast",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					Interface: net.Interface{},
					SockAddr:  sockaddr.MustIPv6Addr("cc::2"),
				},
			},
		},
		{
			name:     "interface-local multicast",
			selector: "interface-local multicast",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					Interface: net.Interface{},
					SockAddr:  sockaddr.MustIPv6Addr("ff01::2"),
				},
			},
		},
		{
			name:     "link-local multicast",
			selector: "link-local multicast",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					Interface: net.Interface{},
					SockAddr:  sockaddr.MustIPv6Addr("ff02::3"),
				},
			},
		},
		{
			name:     "link-local unicast IPv4",
			selector: "link-local unicast",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					Interface: net.Interface{},
					SockAddr:  sockaddr.MustIPv4Addr("169.254.1.101"),
				},
			},
		},
		{
			name:     "link-local unicast IPv6",
			selector: "link-local unicast",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					Interface: net.Interface{},
					SockAddr:  sockaddr.MustIPv6Addr("fe80::3"),
				},
			},
		},
		{
			name:     "loopback ipv4",
			selector: "loopback",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					Interface: net.Interface{
						Flags: net.FlagLoopback,
					},
					SockAddr: sockaddr.MustIPv4Addr("127.0.0.1"),
				},
			},
		},
		{
			name:     "loopback ipv6",
			selector: "loopback",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					Interface: net.Interface{
						Flags: net.FlagLoopback,
					},
					SockAddr: sockaddr.MustIPv6Addr("::1"),
				},
			},
		},
		{
			name:     "multicast IPv4",
			selector: "multicast",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					Interface: net.Interface{
						Flags: net.FlagMulticast,
					},
					SockAddr: sockaddr.MustIPv4Addr("224.0.0.1"),
				},
			},
		},
		{
			name:     "multicast IPv6",
			selector: "multicast",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					Interface: net.Interface{
						Flags: net.FlagMulticast,
					},
					SockAddr: sockaddr.MustIPv6Addr("ff05::3"),
				},
			},
		},
		{
			name:     "point-to-point",
			selector: "point-to-point",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					Interface: net.Interface{
						Flags: net.FlagPointToPoint,
					},
					SockAddr: sockaddr.MustIPv6Addr("cc::3"),
				},
			},
		},
		{
			name:     "unspecified",
			selector: "unspecified",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					Interface: net.Interface{},
					SockAddr:  sockaddr.MustIPv6Addr("::"),
				},
			},
		},
		{
			name:     "up",
			selector: "up",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					Interface: net.Interface{
						Flags: net.FlagUp,
					},
					SockAddr: sockaddr.MustIPv6Addr("cc::3"),
				},
			},
		},
		{
			name:     "invalid",
			selector: "foo",
			fail:     true,
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					Interface: net.Interface{},
					SockAddr:  sockaddr.MustIPv6Addr("cc::3"),
				},
			},
		},
	}

	for i, test := range tests {
		if test.name == "" {
			t.Fatalf("test %d needs a name", i)
		}

		t.Run(test.name, func(t *testing.T) {
			in, out, err := sockaddr.IfByFlag(test.selector, test.ifAddrs)
			if test.fail == true && err == nil {
				t.Fatalf("%s: expected failure", test.name)
			} else if test.fail == true && err != nil {
				return
			}

			if err != nil && test.fail != true {
				t.Fatalf("%s: failed: %v", test.name, err)
			}
			if ilen := len(in); ilen != 1 {
				t.Fatalf("%s: wrong in length %d, expected 1", test.name, ilen)
			}
			if olen := len(out); olen != 0 {
				t.Fatalf("%s: wrong in length %d, expected 0", test.name, olen)
			}
		})
	}
}

func TestIfByNetwork(t *testing.T) {
	tests := []struct {
		name     string
		input    sockaddr.IfAddrs
		selector string
		matched  sockaddr.IfAddrs
		excluded sockaddr.IfAddrs
		fail     bool
	}{
		{
			name: "exact match",
			input: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("1.2.3.4"),
				},
			},
			selector: "1.2.3.4",
			matched: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("1.2.3.4"),
				},
			},
		},
		{
			name: "exact match plural",
			input: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("1.2.3.4"),
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("1.2.3.0/24"),
				},
			},
			selector: "1.2.3.0/24",
			matched: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("1.2.3.4"),
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("1.2.3.0/24"),
				},
			},
		},
		{
			name: "split plural",
			input: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("1.2.3.4"),
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("12.2.3.0/24"),
				},
			},
			selector: "1.2.3.0/24",
			excluded: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("12.2.3.0/24"),
				},
			},
			matched: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("1.2.3.4"),
				},
			},
		},
		{
			name: "excluded plural",
			input: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("1.2.3.4"),
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("12.2.3.0/24"),
				},
			},
			selector: "10.0.0.0/8",
			excluded: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("1.2.3.4"),
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("12.2.3.0/24"),
				},
			},
		},
		{
			name: "invalid selector",
			input: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("1.2.3.4"),
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("12.2.3.0/24"),
				},
			},
			selector: "[:]",
			fail:     true,
		},
	}

	for i, test := range tests {
		if test.name == "" {
			t.Fatalf("test %d needs a name", i)
		}

		t.Run(test.name, func(t *testing.T) {
			matched, excluded, err := sockaddr.IfByNetwork(test.selector, test.input)
			if err != nil && !test.fail {
				t.Fatal("bad")
			} else if err == nil && test.fail {
				t.Fatal("bad")
			}

			if len(test.matched) != len(matched) {
				t.Fatal("bad")
			} else if len(test.excluded) != len(excluded) {
				t.Fatal("bad")
			}

			for i := 0; i < len(test.excluded); i++ {
				if !reflect.DeepEqual(test.excluded[i], excluded[i]) {
					t.Errorf("wrong excluded: %d %v %v", i, test.excluded[i], excluded[i])
				}
			}

			for i := 0; i < len(test.matched); i++ {
				if !reflect.DeepEqual(test.matched[i], matched[i]) {
					t.Errorf("wrong matched: %d %v %v", i, test.matched[i], matched[i])
				}
			}
		})
	}
}

func TestFilterIfByType(t *testing.T) {
	tests := []struct {
		name         string
		ifAddrs      sockaddr.IfAddrs
		ifAddrType   sockaddr.SockAddrType
		matchedLen   int
		remainingLen int
	}{
		{
			name: "include all",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("1.2.3.4"),
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("2.3.4.5"),
				},
			},
			ifAddrType:   sockaddr.TypeIPv4,
			matchedLen:   2,
			remainingLen: 0,
		},
		{
			name: "include some",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("1.2.3.4"),
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv6Addr("::1"),
				},
			},
			ifAddrType:   sockaddr.TypeIPv4,
			matchedLen:   1,
			remainingLen: 1,
		},
		{
			name: "exclude all",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("1.2.3.4"),
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("1.2.3.5"),
				},
			},
			ifAddrType:   sockaddr.TypeIPv6,
			matchedLen:   0,
			remainingLen: 2,
		},
	}

	for i, test := range tests {
		if test.name == "" {
			t.Fatalf("test %d needs a name", i)
		}

		in, out := sockaddr.FilterIfByType(test.ifAddrs, test.ifAddrType)
		if len(in) != test.matchedLen {
			t.Fatalf("%s: wrong length %d, expected %d", test.name, len(in), test.matchedLen)
		}

		if len(out) != test.remainingLen {
			t.Fatalf("%s: wrong length %d, expected %d", test.name, len(out), test.remainingLen)
		}
	}
}

// TestGetIfAddrs runs through the motions of calling sockaddr.GetIfAddrs(), but
// doesn't do much in the way of testing beyond verifying that `lo0` has a
// loopback address present.
func TestGetIfAddrs(t *testing.T) {
	ifAddrs, err := sockaddr.GetAllInterfaces()
	if err != nil {
		t.Fatalf("Unable to proceed: %v", err)
	}
	if len(ifAddrs) == 0 {
		t.Skip()
	}

	var loInt *sockaddr.IfAddr
	for _, ifAddr := range ifAddrs {
		val := sockaddr.IfAddrAttr(ifAddr, "name")
		if val == "" {
			t.Fatalf("name failed")
		} else if val == "lo0" || val == "lo" || val == "Loopback Pseudo-Interface 1" {
			loInt = &ifAddr
			break
		}
	}
	if loInt == nil {
		t.Fatalf("No loopback interfaces found, loInt nil")
	}

	if val := sockaddr.IfAddrAttr(*loInt, "flags"); !(val == "up|loopback|multicast" || val == "up|loopback") {
		t.Fatalf("expected different flags from loopback: %q", val)
	}

	if loInt == nil {
		t.Fatalf("Expected to find an lo0 interface, didn't find any")
	}

	haveIPv4, foundIPv4lo := false, false
	haveIPv6, foundIPv6lo := false, false
	switch loInt.SockAddr.(type) {
	case sockaddr.IPv4Addr:
		haveIPv4 = true

		// Make the semi-brittle assumption that if we have
		// IPv4, we also have an address at 127.0.0.1 available
		// to us.
		if loInt.SockAddr.String() == "127.0.0.1/8" {
			foundIPv4lo = true
		}
	case sockaddr.IPv6Addr:
		haveIPv6 = true
		if loInt.SockAddr.String() == "::1" {
			foundIPv6lo = true
		}
	default:
		t.Fatalf("Unsupported type %v for address %v", loInt.Type(), loInt)
	}

	// While not wise, it's entirely possible a host doesn't have IPv4
	// enabled.
	if haveIPv4 && !foundIPv4lo {
		t.Fatalf("Had an IPv4 w/o an expected IPv4 loopback addresses")
	}

	// While prudent to run without, a sane environment may still contain an
	// IPv6 loopback address.
	if haveIPv6 && !foundIPv6lo {
		t.Fatalf("Had an IPv6 w/o an expected IPv6 loopback addresses")
	}
}

// TestGetDefaultIfName tests to make sure a default interface name is always
// returned from getDefaultIfName().
func TestGetDefaultInterface(t *testing.T) {
	reportOnDefault := func(args ...interface{}) {
		if havePublicIP() || havePrivateIP() {
			t.Fatalf(args[0].(string), args[1:]...)
		} else {
			t.Skipf(args[0].(string), args[1:]...)
		}
	}

	ifAddrs, err := sockaddr.GetDefaultInterfaces()
	if err != nil {
		switch {
		case len(ifAddrs) == 0:
			reportOnDefault("bad: %v", err)
		case ifAddrs[0].Flags&net.FlagUp == 0:
			reportOnDefault("bad: %v", err)
		default:
			reportOnDefault("bad: %v", err)
		}
	}
}

func TestIfAddrAttrs(t *testing.T) {
	const expectedNumAttrs = 2
	attrs := sockaddr.IfAddrAttrs()
	if len(attrs) != expectedNumAttrs {
		t.Fatalf("wrong number of attrs")
	}

	tests := []struct {
		name     string
		ifAddr   sockaddr.IfAddr
		attr     string
		expected string
	}{
		{
			name: "name",
			ifAddr: sockaddr.IfAddr{
				Interface: net.Interface{
					Name: "abc0",
				},
			},
			attr:     "name",
			expected: "abc0",
		},
	}

	for i, test := range tests {
		if test.name == "" {
			t.Fatalf("test %d must have a name", i)
		}

		result, err := sockaddr.IfAttrs(test.attr, sockaddr.IfAddrs{test.ifAddr})
		if err != nil {
			t.Errorf("failed to get attr %q from %v", test.name, test.ifAddr)
		}

		if result != test.expected {
			t.Errorf("unexpected result")
		}
	}

	// Test an empty array
	result, err := sockaddr.IfAttrs("name", sockaddr.IfAddrs{})
	if err != nil {
		t.Error(`failed to get attr "name" from an empty array`)
	}

	if result != "" {
		t.Errorf("unexpected result")
	}
}

func TestGetAllInterfaces(t *testing.T) {
	ifAddrs, err := sockaddr.GetAllInterfaces()
	if err != nil {
		t.Fatalf("unable to gather interfaces: %v", err)
	}

	initialLen := len(ifAddrs)
	if initialLen == 0 {
		t.Fatalf("no interfaces available")
	}

	ifAddrs, err = sockaddr.SortIfBy("name,type,port,size,address", ifAddrs)
	if err != nil {
		t.Fatalf("unable to initially sort address")
	}

	ascSorted, err := sockaddr.SortIfBy("name,type,port,size,address", ifAddrs)
	if err != nil {
		t.Fatalf("unable to asc sort address")
	}

	descSorted, err := sockaddr.SortIfBy("name,type,port,size,-address", ascSorted)
	if err != nil {
		t.Fatalf("unable to desc sort address")
	}

	if initialLen != len(ascSorted) && len(ascSorted) != len(descSorted) {
		t.Fatalf("wrong len")
	}

	for i := initialLen - 1; i >= 0; i-- {
		if !reflect.DeepEqual(descSorted[i], ifAddrs[i]) {
			t.Errorf("wrong sort order: %d %v %v", i, descSorted[i], ifAddrs[i])
		}
	}
}

func TestGetDefaultInterfaces(t *testing.T) {
	reportOnDefault := func(args ...interface{}) {
		if havePublicIP() || havePrivateIP() {
			t.Fatalf(args[0].(string), args[1:]...)
		} else {
			t.Skipf(args[0].(string), args[1:]...)
		}
	}

	ifAddrs, err := sockaddr.GetDefaultInterfaces()
	if err != nil {
		reportOnDefault("unable to gather default interfaces: %v", err)
	}

	if len(ifAddrs) == 0 {
		reportOnDefault("no default interfaces available", nil)
	}
}

func TestGetPrivateInterfaces(t *testing.T) {
	reportOnPrivate := func(args ...interface{}) {
		if havePrivateIP() {
			t.Fatalf(args[0].(string), args[1:]...)
		} else {
			t.Skipf(args[0].(string), args[1:]...)
		}
	}

	ifAddrs, err := sockaddr.GetPrivateInterfaces()
	if err != nil {
		reportOnPrivate("failed: %v", err)
	}

	if len(ifAddrs) == 0 {
		reportOnPrivate("no public IPs found")
	}

	if len(ifAddrs[0].String()) == 0 {
		reportOnPrivate("no string representation of private IP found")
	}
}

func TestGetPublicInterfaces(t *testing.T) {
	reportOnPublic := func(args ...interface{}) {
		if havePublicIP() {
			t.Fatalf(args[0].(string), args[1:]...)
		} else {
			t.Skipf(args[0].(string), args[1:]...)
		}
	}

	ifAddrs, err := sockaddr.GetPublicInterfaces()
	if err != nil {
		reportOnPublic("failed: %v", err)
	}

	if len(ifAddrs) == 0 {
		reportOnPublic("no public IPs found")
	}
}

func TestIncludeExcludeIfs(t *testing.T) {
	tests := []struct {
		name         string
		ifAddrs      sockaddr.IfAddrs
		fail         bool
		excludeNum   int
		excludeName  string
		excludeParam string
		includeName  string
		includeParam string
		includeNum   int
	}{
		{
			name: "address",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("1.2.3.4"),
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("2.3.4.5"),
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("3.4.5.6"),
				},
			},
			excludeName:  "address",
			excludeNum:   2,
			excludeParam: `^1\..*\.4$`,
			includeName:  "address",
			includeNum:   1,
			includeParam: `^1\.2\.3\.`,
		},
		{
			name:         "address invalid",
			fail:         true,
			excludeName:  "address",
			excludeNum:   0,
			excludeParam: `*`,
			includeName:  "address",
			includeNum:   0,
			includeParam: `[`,
		},
		{
			name: "flag",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					Interface: net.Interface{
						Flags: net.FlagUp | net.FlagLoopback,
					},
				},
				sockaddr.IfAddr{
					Interface: net.Interface{
						Flags: net.FlagLoopback,
					},
				},
				sockaddr.IfAddr{
					Interface: net.Interface{
						Flags: net.FlagMulticast,
					},
				},
			},
			excludeName:  "flags",
			excludeNum:   2,
			excludeParam: `up|loopback`,
			includeName:  "flags",
			includeNum:   2,
			includeParam: `loopback`,
		},
		{
			name:         "flag invalid",
			fail:         true,
			excludeName:  "foo",
			excludeNum:   0,
			excludeParam: `*`,
			includeName:  "bar",
			includeNum:   0,
			includeParam: `[`,
		},
		{
			name: "name",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					Interface: net.Interface{
						Name: "abc0",
					},
				},
				sockaddr.IfAddr{
					Interface: net.Interface{
						Name: "xyz0",
					},
				},
				sockaddr.IfAddr{
					Interface: net.Interface{
						Name: "docker666",
					},
				},
			},
			excludeName:  "name",
			excludeNum:   2,
			excludeParam: `^docker[\d]+$`,
			includeName:  "name",
			includeNum:   2,
			includeParam: `^([a-z]+)0$`,
		},
		{
			name:         "name invalid",
			fail:         true,
			excludeName:  "name",
			excludeNum:   0,
			excludeParam: `*`,
			includeName:  "name",
			includeNum:   0,
			includeParam: `[`,
		},
		{
			name: "network",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("10.2.3.4/24"),
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("10.255.255.4/24"),
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPAddr("::1"),
				},
			},
			excludeName:  "network",
			excludeNum:   1,
			excludeParam: `10.0.0.0/8`,
			includeName:  "network",
			includeNum:   1,
			includeParam: `::/127`,
		},
		{
			name: "port",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("1.2.3.4:8600"),
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("2.3.4.5:4646"),
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("3.4.5.6:4647"),
				},
			},
			excludeName:  "port",
			excludeNum:   2,
			excludeParam: `0$`,
			includeName:  "port",
			includeNum:   2,
			includeParam: `^46[\d]{2}$`,
		},
		{
			name:         "port invalid",
			fail:         true,
			excludeName:  "port",
			excludeNum:   0,
			excludeParam: `*`,
			includeName:  "port",
			includeNum:   0,
			includeParam: `[`,
		},
		{
			name: "rfc",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("10.2.3.4/24"),
				},
				sockaddr.IfAddr{
					// Excluded (/127 vs /128)
					SockAddr: sockaddr.MustIPv6Addr("::1/127"),
				},
				sockaddr.IfAddr{
					// Excluded (/127 vs /128)
					SockAddr: sockaddr.MustIPv6Addr("::/127"),
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPAddr("203.0.113.0/24"),
				},
			},
			excludeName:  "rfc",
			excludeNum:   2,
			excludeParam: `6890`,
			includeName:  "rfc",
			includeNum:   1,
			includeParam: `3330`,
		},
		{
			name:         "rfc invalid",
			fail:         true,
			excludeName:  "rfc",
			excludeNum:   0,
			excludeParam: `rfcOneTwoThree`,
			includeName:  "rfc",
			includeNum:   0,
			includeParam: `99999999999999`,
		},
		{
			name: "rfc IPv4 exclude",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("192.169.1.1"),
				},
			},
			excludeName:  "rfc",
			excludeNum:   1,
			excludeParam: `1918`,
			includeName:  "rfc",
			includeNum:   0,
			includeParam: `1918`,
		},
		{
			name: "rfc IPv4 include",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("192.168.1.1"),
				},
			},
			excludeName:  "rfc",
			excludeNum:   0,
			excludeParam: `1918`,
			includeName:  "rfc",
			includeNum:   1,
			includeParam: `1918`,
		},
		{
			name: "rfc IPv4 excluded RFCs",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("192.168.1.1"),
				},
			},
			excludeName:  "rfc",
			excludeNum:   1,
			excludeParam: `4291`,
			includeName:  "rfc",
			includeNum:   0,
			includeParam: `4291`,
		},
		{
			name: "rfc IPv6 exclude",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv6Addr("cc::1/127"),
				},
			},
			excludeName:  "rfc",
			excludeNum:   1,
			excludeParam: `4291`,
			includeName:  "rfc",
			includeNum:   0,
			includeParam: `4291`,
		},
		{
			name: "rfc IPv6 include",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv6Addr("::1/127"),
				},
			},
			excludeName:  "rfc",
			excludeNum:   0,
			excludeParam: `4291`,
			includeName:  "rfc",
			includeNum:   1,
			includeParam: `4291`,
		},
		{
			name: "rfc zero match",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("1.2.3.4"),
				},
			},
			excludeName:  "rfc",
			excludeNum:   1,
			excludeParam: `1918`,
			includeName:  "rfc",
			includeNum:   0,
			includeParam: `1918`,
		},
		{
			name:         "rfc empty list",
			ifAddrs:      sockaddr.IfAddrs{},
			excludeName:  "rfc",
			excludeNum:   0,
			excludeParam: `4291`,
			includeName:  "rfc",
			includeNum:   0,
			includeParam: `1918`,
		},
		{
			name: "size",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("10.2.3.4/24"),
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPAddr("203.0.113.0/24"),
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv6Addr("::1/24"),
				},
			},
			excludeName:  "size",
			excludeParam: `24`,
			excludeNum:   0,
			includeName:  "size",
			includeParam: `24`,
			includeNum:   3,
		},
		{
			name: "size invalid",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("10.2.3.4/24"),
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv6Addr("::1/128"),
				},
			},
			fail:         true,
			excludeName:  "size",
			excludeParam: `33`,
			excludeNum:   0,
			includeName:  "size",
			includeParam: `-1`,
			includeNum:   0,
		},
		{
			name: "type",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("10.2.3.4/24"),
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPAddr("203.0.113.0/24"),
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv6Addr("::1/127"),
				},
			},
			excludeName:  "type",
			excludeParam: `ipv6`,
			excludeNum:   2,
			includeName:  "type",
			includeParam: `ipv4`,
			includeNum:   2,
		},
		{
			name: "type",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPv4Addr("10.2.3.4/24"),
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPAddr("::1"),
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustUnixSock("/tmp/foo"),
				},
			},
			excludeName:  "type",
			excludeParam: `ip`,
			excludeNum:   1,
			includeName:  "type",
			includeParam: `unix`,
			includeNum:   1,
		},
		{
			name:         "type invalid arg",
			fail:         true,
			excludeName:  "type",
			excludeParam: `*`,
			excludeNum:   0,
			includeName:  "type",
			includeParam: `[`,
			includeNum:   0,
		},
		{
			name:         "type invalid",
			fail:         true,
			excludeName:  "foo",
			excludeParam: `bar`,
			excludeNum:   0,
			includeName:  "baz",
			includeParam: `bur`,
			includeNum:   0,
		},
	}

	for i, test := range tests {
		if test.name == "" {
			t.Fatalf("test %d must have a name", i)
		}
		t.Run(fmt.Sprintf("%s-%s", test.name, "include"), func(t *testing.T) {
			t.Logf("test.ifAddrs: %v", test.ifAddrs)
			inIfAddrs, err := sockaddr.IncludeIfs(test.includeName, test.includeParam, test.ifAddrs)
			t.Logf("inIfAddrs: %v", inIfAddrs)

			switch {
			case !test.fail && err != nil:
				t.Errorf("%s: failed unexpectedly: %v", test.name, err)
			case test.fail && err == nil:
				t.Errorf("%s: failed to throw an error", test.name)
			case test.fail && err != nil:
				// expected test failure
				return
			}

			if len(inIfAddrs) != test.includeNum {
				t.Errorf("%s: failed include length check. Expected %d, got %d. Input: %q", test.name, test.includeNum, len(inIfAddrs), test.includeParam)
			}
		})

		t.Run(fmt.Sprintf("%s-%s", test.name, "exclude"), func(t *testing.T) {
			t.Logf("test.ifAddrs: %v", test.ifAddrs)
			outIfAddrs, err := sockaddr.ExcludeIfs(test.excludeName, test.excludeParam, test.ifAddrs)
			t.Logf("outIfAddrs: %v", outIfAddrs)

			switch {
			case !test.fail && err != nil:
				t.Errorf("%s: failed unexpectedly: %v", test.name, err)
			case test.fail && err == nil:
				t.Errorf("%s: failed to throw an error", test.name)
			case test.fail && err != nil:
				// expected test failure
				return
			}

			if len(outIfAddrs) != test.excludeNum {
				t.Errorf("%s: failed exclude length check. Expected %d, got %d. Input: %q", test.name, test.excludeNum, len(outIfAddrs), test.excludeParam)
			}
		})
	}
}

func TestNewIPAddr(t *testing.T) {
	tests := []struct {
		name   string
		input  string
		output string
		pass   bool
	}{
		{
			name:   "ipv4",
			input:  "1.2.3.4",
			output: "1.2.3.4",
			pass:   true,
		},
		{
			name:   "ipv6",
			input:  "::1",
			output: "::1",
			pass:   true,
		},
		{
			name:   "invalid",
			input:  "255.255.255.256",
			output: "",
			pass:   false,
		},
	}

	for _, test := range tests {
		ip, err := sockaddr.NewIPAddr(test.input)
		switch {
		case err == nil && test.pass,
			err != nil && !test.pass:

		default:
			t.Errorf("expected %s's success to be %t", test.input, test.pass)
		}

		if !test.pass {
			continue
		}

		ipStr := ip.String()
		if ipStr != test.output {
			t.Errorf("Expected %q to match %q", test.input, test.output, ipStr)
		}

	}
}

func TestIPAttrs(t *testing.T) {
	const expectedIPAttrs = 11
	ipAttrs := sockaddr.IPAttrs()
	if len(ipAttrs) != expectedIPAttrs {
		t.Fatalf("wrong number of args")
	}
}

func TestUniqueIfAddrsBy(t *testing.T) {
	tests := []struct {
		name     string
		ifAddrs  sockaddr.IfAddrs
		fail     bool
		selector string
		expected []string
	}{
		{
			name: "address",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPAddr("203.0.113.0/24"),
					Interface: net.Interface{
						Name: "abc0",
					},
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPAddr("203.0.113.0/24"),
					Interface: net.Interface{
						Name: "abc0",
					},
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPAddr("10.2.3.4"),
					Interface: net.Interface{
						Name: "foo1",
					},
				},
			},
			selector: "address",
			expected: []string{"203.0.113.0/24 {0 0 abc0  0}", "10.2.3.4 {0 0 foo1  0}"},
		},
		{
			name: "name",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPAddr("::1"),
					Interface: net.Interface{
						Name: "lo0",
					},
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPAddr("fe80::1"),
					Interface: net.Interface{
						Name: "lo0",
					},
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPAddr("127.0.0.1"),
					Interface: net.Interface{
						Name: "foo1",
					},
				},
			},
			selector: "name",
			expected: []string{"::1 {0 0 lo0  0}", "127.0.0.1 {0 0 foo1  0}"},
		},
		{
			name: "invalid",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{},
			},
			fail:     true,
			selector: "goozfraba",
			expected: []string{},
		},
	}

	for i, test := range tests {
		if test.name == "" {
			t.Fatalf("test %d must have a name", i)
		}
		t.Run(test.name, func(t *testing.T) {

			uniqueAddrs, err := sockaddr.UniqueIfAddrsBy(test.selector, test.ifAddrs)
			switch {
			case !test.fail && err != nil:
				t.Fatalf("%s: failed unexpectedly: %v", test.name, err)
			case test.fail && err == nil:
				t.Fatalf("%s: failed to throw an error", test.name)
			case test.fail && err != nil:
				// expected test failure
				return
			}

			if len(uniqueAddrs) != len(test.expected) {
				t.Fatalf("%s: failed uniquify by attribute %s", test.name, test.selector)
			}

			for i := 0; i < len(uniqueAddrs); i++ {
				got := uniqueAddrs[i].String()
				if got != test.expected[i] {
					t.Fatalf("%s: expected %q got %q", test.name, test.expected[i], got)
				}
			}

		})
	}
}

func TestJoinIfAddrsBy(t *testing.T) {
	tests := []struct {
		name     string
		ifAddrs  sockaddr.IfAddrs
		fail     bool
		selector string
		joinStr  string
		expected string
	}{
		{
			name: "address",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPAddr("203.0.113.0/24"),
					Interface: net.Interface{
						Name: "abc0",
					},
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPAddr("203.0.113.1"),
					Interface: net.Interface{
						Name: "abc0",
					},
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPAddr("10.2.3.4"),
					Interface: net.Interface{
						Name: "foo1",
					},
				},
			},
			selector: "address",
			joinStr:  " ",
			expected: "203.0.113.0 203.0.113.1 10.2.3.4",
		},
		{
			name: "name",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPAddr("::1"),
					Interface: net.Interface{
						Name: "lo0",
					},
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPAddr("fe80::1"),
					Interface: net.Interface{
						Name: "foo0",
					},
				},
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPAddr("127.0.0.1"),
					Interface: net.Interface{
						Name: "bar2",
					},
				},
			},
			selector: "name",
			joinStr:  "-/-",
			expected: "lo0-/-foo0-/-bar2",
		},
		{
			name: "invalid",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr: sockaddr.MustIPAddr("127.0.0.1"),
					Interface: net.Interface{
						Name: "bar2",
					},
				},
			},
			fail:     true,
			selector: "goozfraba",
			expected: "",
		},
	}

	for i, test := range tests {
		if test.name == "" {
			t.Fatalf("test %d must have a name", i)
		}
		t.Run(test.name, func(t *testing.T) {

			result, err := sockaddr.JoinIfAddrs(test.selector, test.joinStr, test.ifAddrs)
			switch {
			case !test.fail && err != nil:
				t.Fatalf("%s: failed unexpectedly: %v", test.name, err)
			case test.fail && err == nil:
				t.Fatalf("%s: failed to throw an error", test.name)
			case test.fail && err != nil:
				// expected test failure
				return
			}

			if result != test.expected {
				t.Fatalf("%s: expected %q got %q", test.name, test.expected, result)
			}

		})
	}
}

func TestLimitOffset(t *testing.T) {
	tests := []struct {
		name     string
		ifAddrs  sockaddr.IfAddrs
		limit    uint
		offset   int
		fail     bool
		expected sockaddr.IfAddrs
	}{
		{
			name: "basic limit offset",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPAddr("203.0.113.0/24")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPAddr("203.0.113.1")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPAddr("203.0.113.2")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPAddr("203.0.113.3")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPAddr("203.0.113.4")},
			},
			limit:  2,
			offset: 1,
			expected: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPAddr("203.0.113.1")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPAddr("203.0.113.2")},
			},
		},
		{
			name: "negative offset with limit",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPAddr("203.0.113.0/24")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPAddr("203.0.113.1")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPAddr("203.0.113.2")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPAddr("203.0.113.3")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPAddr("203.0.113.4")},
			},
			limit:  2,
			offset: -3,
			expected: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPAddr("203.0.113.2")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPAddr("203.0.113.3")},
			},
		},
		{
			name: "large limit",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPAddr("203.0.113.0/24")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPAddr("203.0.113.1")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPAddr("203.0.113.2")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPAddr("203.0.113.3")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPAddr("203.0.113.4")},
			},
			limit:  100,
			offset: 3,
			expected: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPAddr("203.0.113.3")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPAddr("203.0.113.4")},
			},
		},
		{
			name: "bigger offset than size",
			ifAddrs: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPAddr("203.0.113.0/24")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPAddr("203.0.113.1")},
			},
			fail:   true,
			limit:  1,
			offset: 3,
		},
	}

	for i, test := range tests {
		if test.name == "" {
			t.Fatalf("test %d must have a name", i)
		}
		t.Run(test.name, func(t *testing.T) {

			offsetResults, err := sockaddr.OffsetIfAddrs(test.offset, test.ifAddrs)
			switch {
			case !test.fail && err != nil:
				t.Fatalf("%s: failed unexpectedly: %v", test.name, err)
			case test.fail && err == nil:
				t.Fatalf("%s: failed to throw an error", test.name)
			case test.fail && err != nil:
				// expected test failure
				return
			}

			limitResults, err := sockaddr.LimitIfAddrs(test.limit, offsetResults)
			switch {
			case !test.fail && err != nil:
				t.Fatalf("%s: failed unexpectedly: %v", test.name, err)
			case test.fail && err == nil:
				t.Fatalf("%s: failed to throw an error", test.name)
			case test.fail && err != nil:
				// expected test failure
				return
			}

			if len(test.expected) != len(limitResults) {
				t.Fatalf("bad")
			}

			for i := 0; i < len(test.expected); i++ {
				if !reflect.DeepEqual(limitResults[i], test.expected[i]) {
					t.Errorf("objects in ordered limit")
				}
			}
		})
	}
}

func TestSortIfBy(t *testing.T) {
	tests := []struct {
		name    string
		sortStr string
		in      sockaddr.IfAddrs
		out     sockaddr.IfAddrs
		fail    bool
	}{
		{
			name:    "sort address",
			sortStr: "address",
			in: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.4")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.3")},
			},
			out: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.3")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.4")},
			},
		},
		{
			name:    "sort +address",
			sortStr: "+address",
			in: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.4")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.3")},
			},
			out: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.3")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.4")},
			},
		},
		{
			name:    "sort -address",
			sortStr: "-address",
			in: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.3")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.4")},
			},
			out: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.4")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.3")},
			},
		},
		{
			// NOTE(seanc@): This test requires macOS, or at least a computer where
			// en0 has the default route.
			name:    "sort default",
			sortStr: "default",
			in: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr:  sockaddr.MustIPv4Addr("1.2.3.4"),
					Interface: net.Interface{Name: ifNameWithDefault},
				},
				sockaddr.IfAddr{
					SockAddr:  sockaddr.MustIPv4Addr("1.2.3.3"),
					Interface: net.Interface{Name: "other0"},
				},
			},
			out: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr:  sockaddr.MustIPv4Addr("1.2.3.4"),
					Interface: net.Interface{Name: ifNameWithDefault},
				},
				sockaddr.IfAddr{
					SockAddr:  sockaddr.MustIPv4Addr("1.2.3.3"),
					Interface: net.Interface{Name: "other0"},
				},
			},
		},
		{
			// NOTE(seanc@): This test requires macOS, or at least a computer where
			// en0 has the default route.
			name:    "sort +default",
			sortStr: "+default",
			in: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr:  sockaddr.MustIPv4Addr("1.2.3.4"),
					Interface: net.Interface{Name: "other0"},
				},
				sockaddr.IfAddr{
					SockAddr:  sockaddr.MustIPv4Addr("1.2.3.3"),
					Interface: net.Interface{Name: ifNameWithDefault},
				},
			},
			out: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr:  sockaddr.MustIPv4Addr("1.2.3.3"),
					Interface: net.Interface{Name: ifNameWithDefault},
				},
				sockaddr.IfAddr{
					SockAddr:  sockaddr.MustIPv4Addr("1.2.3.4"),
					Interface: net.Interface{Name: "other0"},
				},
			},
		},
		{
			name:    "sort -default",
			sortStr: "-default",
			in: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr:  sockaddr.MustIPv4Addr("1.2.3.3"),
					Interface: net.Interface{Name: ifNameWithDefault},
				},
				sockaddr.IfAddr{
					SockAddr:  sockaddr.MustIPv4Addr("1.2.3.4"),
					Interface: net.Interface{Name: "other0"},
				},
			},
			out: sockaddr.IfAddrs{
				sockaddr.IfAddr{
					SockAddr:  sockaddr.MustIPv4Addr("1.2.3.4"),
					Interface: net.Interface{Name: "other0"},
				},
				sockaddr.IfAddr{
					SockAddr:  sockaddr.MustIPv4Addr("1.2.3.3"),
					Interface: net.Interface{Name: ifNameWithDefault},
				},
			},
		},
		{
			name:    "sort name",
			sortStr: "name",
			in: sockaddr.IfAddrs{
				sockaddr.IfAddr{Interface: net.Interface{Name: "foo"}},
				sockaddr.IfAddr{Interface: net.Interface{Name: "bar"}},
			},
			out: sockaddr.IfAddrs{
				sockaddr.IfAddr{Interface: net.Interface{Name: "bar"}},
				sockaddr.IfAddr{Interface: net.Interface{Name: "foo"}},
			},
		},
		{
			name:    "sort +name",
			sortStr: "+name",
			in: sockaddr.IfAddrs{
				sockaddr.IfAddr{Interface: net.Interface{Name: "foo"}},
				sockaddr.IfAddr{Interface: net.Interface{Name: "bar"}},
			},
			out: sockaddr.IfAddrs{
				sockaddr.IfAddr{Interface: net.Interface{Name: "bar"}},
				sockaddr.IfAddr{Interface: net.Interface{Name: "foo"}},
			},
		},
		{
			name:    "sort -name",
			sortStr: "-name",
			in: sockaddr.IfAddrs{
				sockaddr.IfAddr{Interface: net.Interface{Name: "bar"}},
				sockaddr.IfAddr{Interface: net.Interface{Name: "foo"}},
			},
			out: sockaddr.IfAddrs{
				sockaddr.IfAddr{Interface: net.Interface{Name: "foo"}},
				sockaddr.IfAddr{Interface: net.Interface{Name: "bar"}},
			},
		},
		{
			name:    "sort port",
			sortStr: "port",
			in: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.4:80")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv6Addr("[::1]:53")},
			},
			out: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv6Addr("[::1]:53")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.4:80")},
			},
		},
		{
			name:    "sort +port",
			sortStr: "+port",
			in: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.4:80")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv6Addr("[::1]:53")},
			},
			out: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv6Addr("[::1]:53")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.4:80")},
			},
		},
		{
			name:    "sort -port",
			sortStr: "-port",
			in: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv6Addr("[::1]:53")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.4:80")},
			},
			out: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.4:80")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv6Addr("[::1]:53")},
			},
		},
		{
			name:    "sort private",
			sortStr: "private",
			in: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.4:80")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("192.168.1.1")},
			},
			out: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("192.168.1.1")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.4:80")},
			},
		},
		{
			name:    "sort +private",
			sortStr: "+private",
			in: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.4:80")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("192.168.1.1")},
			},
			out: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("192.168.1.1")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.4:80")},
			},
		},
		{
			name:    "sort -private",
			sortStr: "-private",
			in: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("192.168.1.1")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.4:80")},
			},
			out: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.4:80")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("192.168.1.1")},
			},
		},
		{
			name:    "sort size",
			sortStr: "size",
			in: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("192.168.1.1/27")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.4:80")},
			},
			out: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("192.168.1.1/27")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.4:80")},
			},
		},
		{
			name:    "sort +size",
			sortStr: "+size",
			in: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("192.168.1.1/27")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.4:80")},
			},
			out: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("192.168.1.1/27")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.4:80")},
			},
		},
		{
			name:    "sort -size",
			sortStr: "-size",
			in: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("192.168.1.1/27")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.4:80")},
			},
			out: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.4:80")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("192.168.1.1/27")},
			},
		},
		{
			name:    "sort type",
			sortStr: "type",
			in: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv6Addr("::1")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("192.168.1.1/27")},
			},
			out: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("192.168.1.1/27")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv6Addr("::1")},
			},
		},
		{
			name:    "sort +type",
			sortStr: "+type",
			in: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv6Addr("::1")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("192.168.1.1/27")},
			},
			out: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("192.168.1.1/27")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv6Addr("::1")},
			},
		},
		{
			name:    "sort -type",
			sortStr: "-type",
			in: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv6Addr("::1")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.4:80")},
			},
			out: sockaddr.IfAddrs{
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv6Addr("::1")},
				sockaddr.IfAddr{SockAddr: sockaddr.MustIPv4Addr("1.2.3.4:80")},
			},
		},
		{
			name:    "sort invalid",
			sortStr: "ENOENT",
			fail:    true,
		},
	}

	for i, test := range tests {
		if test.name == "" {
			t.Fatalf("test %i needs a name", i)
		}

		t.Run(test.name, func(t *testing.T) {
			sorted, err := sockaddr.SortIfBy(test.sortStr, test.in)
			if err != nil && !test.fail {
				t.Fatalf("%s: sort failed: %v", test.name, err)
			}

			if len(test.in) != len(sorted) {
				t.Fatalf("wrong len")
			}

			for i := 0; i < len(sorted); i++ {
				if !reflect.DeepEqual(sorted[i], test.out[i]) {
					t.Errorf("wrong sort order: %d %v %v", i, sorted[i], test.out[i])
				}
			}
		})
	}
}
