...

Source file src/go4.org/netipx/netipx_test.go

Documentation: go4.org/netipx

     1  // Copyright 2020 The Inet.Af AUTHORS. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //lint:file-ignore U1000 allow unused code in tests for experiments.
     6  
     7  package netipx
     8  
     9  import (
    10  	"bytes"
    11  	"encoding"
    12  	"flag"
    13  	"net"
    14  	"net/netip"
    15  	"reflect"
    16  	"testing"
    17  )
    18  
    19  type (
    20  	IPPrefix = netip.Prefix
    21  	IP       = netip.Addr
    22  	IPPort   = netip.AddrPort
    23  )
    24  
    25  // IPv4 returns the IP of the IPv4 address a.b.c.d.
    26  func IPv4(a, b, c, d uint8) IP {
    27  	return netip.AddrFrom4([4]byte{a, b, c, d})
    28  }
    29  
    30  var long = flag.Bool("long", false, "run long tests")
    31  
    32  func TestFromStdIP(t *testing.T) {
    33  	mustFromStdIPMustPanic := func(std net.IP) (IP, bool) {
    34  		defer func() {
    35  			if r := recover(); r == nil {
    36  				t.Errorf("expected MustFromStdIP(%#v) to panic", std)
    37  			}
    38  		}()
    39  		return MustFromStdIP(std), true
    40  	}
    41  
    42  	tests := []struct {
    43  		name string
    44  		fn   func(net.IP) (IP, bool)
    45  		std  net.IP
    46  		want IP
    47  	}{
    48  		{
    49  			name: "v4",
    50  			fn:   FromStdIP,
    51  			std:  []byte{1, 2, 3, 4},
    52  			want: IPv4(1, 2, 3, 4),
    53  		},
    54  		{
    55  			name: "v6",
    56  			fn:   FromStdIP,
    57  			std:  net.ParseIP("::1"),
    58  			want: netip.AddrFrom16([...]byte{15: 1}),
    59  		},
    60  		{
    61  			name: "4in6-unmap",
    62  			fn:   FromStdIP,
    63  			std:  net.ParseIP("1.2.3.4"),
    64  			want: IPv4(1, 2, 3, 4),
    65  		},
    66  		{
    67  			name: "v4-raw",
    68  			fn:   FromStdIPRaw,
    69  			std:  net.ParseIP("1.2.3.4").To4(),
    70  			want: IPv4(1, 2, 3, 4),
    71  		},
    72  		{
    73  			name: "4in6-raw",
    74  			fn:   FromStdIPRaw,
    75  			std:  net.ParseIP("1.2.3.4"),
    76  			want: netip.AddrFrom16([...]byte{10: 0xff, 11: 0xff, 12: 1, 13: 2, 14: 3, 15: 4}),
    77  		},
    78  		{
    79  			name: "bad-raw",
    80  			fn:   FromStdIPRaw,
    81  			std:  net.IP{0xff},
    82  		},
    83  		{
    84  			name: "bad-must-panic",
    85  			fn:   mustFromStdIPMustPanic,
    86  			std:  net.IP{0x00, 0x01, 0x02},
    87  		},
    88  	}
    89  	for _, tt := range tests {
    90  		t.Run(tt.name, func(t *testing.T) {
    91  			got, ok := tt.fn(tt.std)
    92  			if got != tt.want {
    93  				t.Errorf("got (%#v, %v); want %#v", got, ok, tt.want)
    94  			}
    95  		})
    96  	}
    97  }
    98  
    99  func TestFromStdAddr(t *testing.T) {
   100  	tests := []struct {
   101  		name   string
   102  		std    net.IP
   103  		port   int
   104  		zone   string
   105  		want   netip.AddrPort
   106  		wantOK bool
   107  	}{
   108  		{
   109  			name: "invalid IP",
   110  			std:  net.IP{0xff},
   111  		},
   112  		{
   113  			name: "invalid port",
   114  			std:  net.IP{1, 2, 3, 4},
   115  			port: -1,
   116  		},
   117  		{
   118  			name:   "v4",
   119  			std:    net.IP{1, 2, 3, 4},
   120  			port:   8080,
   121  			want:   netip.AddrPortFrom(netip.AddrFrom4([...]byte{1, 2, 3, 4}), 8080),
   122  			wantOK: true,
   123  		},
   124  		{
   125  			name: "v4 with zone",
   126  			std:  net.IP{1, 2, 3, 4},
   127  			port: 8080,
   128  			zone: "foobar",
   129  		},
   130  		{
   131  			name:   "v6",
   132  			std:    net.ParseIP("fc::"),
   133  			port:   8080,
   134  			want:   netip.AddrPortFrom(netip.MustParseAddr("fc::"), 8080),
   135  			wantOK: true,
   136  		},
   137  		{
   138  			name:   "v6 with zone",
   139  			std:    net.ParseIP("fc::"),
   140  			port:   8080,
   141  			zone:   "foobar",
   142  			want:   netip.AddrPortFrom(netip.MustParseAddr("fc::").WithZone("foobar"), 8080),
   143  			wantOK: true,
   144  		},
   145  	}
   146  	for _, tt := range tests {
   147  		t.Run(tt.name, func(t *testing.T) {
   148  			got, ok := FromStdAddr(tt.std, tt.port, tt.zone)
   149  			if got != tt.want || ok != tt.wantOK {
   150  				t.Errorf("FromStdAddr(%#v, %d, %q): got (%#v, %v); want (%#v, %t)", tt.std, tt.port, tt.zone, got, ok, tt.want, tt.wantOK)
   151  			}
   152  		})
   153  	}
   154  }
   155  
   156  func TestFromStdIPNet(t *testing.T) {
   157  	tests := []struct {
   158  		name string
   159  		std  *net.IPNet
   160  		want IPPrefix
   161  	}{
   162  		{
   163  			name: "invalid IP",
   164  			std: &net.IPNet{
   165  				IP: net.IP{0xff},
   166  			},
   167  		},
   168  		{
   169  			name: "invalid mask",
   170  			std: &net.IPNet{
   171  				IP:   net.IPv6loopback,
   172  				Mask: nil,
   173  			},
   174  		},
   175  		{
   176  			name: "non-contiguous mask",
   177  			std: &net.IPNet{
   178  				IP:   net.IPv4(192, 0, 2, 0).To4(),
   179  				Mask: net.IPv4Mask(255, 0, 255, 0),
   180  			},
   181  		},
   182  		{
   183  			name: "IPv4",
   184  			std: &net.IPNet{
   185  				IP:   net.IPv4(192, 0, 2, 0).To4(),
   186  				Mask: net.CIDRMask(24, 32),
   187  			},
   188  			want: mustIPPrefix("192.0.2.0/24"),
   189  		},
   190  		{
   191  			name: "IPv6",
   192  			std: &net.IPNet{
   193  				IP:   net.ParseIP("2001:db8::"),
   194  				Mask: net.CIDRMask(64, 128),
   195  			},
   196  			want: mustIPPrefix("2001:db8::/64"),
   197  		},
   198  	}
   199  	for _, tt := range tests {
   200  		t.Run(tt.name, func(t *testing.T) {
   201  			got, ok := FromStdIPNet(tt.std)
   202  			if !ok && got != (IPPrefix{}) {
   203  				t.Fatalf("!ok but non-zero result")
   204  			}
   205  
   206  			if got != tt.want {
   207  				t.Errorf("FromStdIPNet(%q) = %+v; want %+v", tt.std, got, tt.want)
   208  			}
   209  		})
   210  	}
   211  }
   212  
   213  func TestAddrIPNet(t *testing.T) {
   214  	tests := []struct {
   215  		name string
   216  		addr netip.Addr
   217  		want *net.IPNet
   218  	}{
   219  		{
   220  			name: "invalid IP",
   221  			addr: netip.Addr{},
   222  			want: &net.IPNet{},
   223  		},
   224  		{
   225  			name: "IPv4",
   226  			addr: mustIP("127.0.0.1"),
   227  			want: &net.IPNet{
   228  				IP:   net.IPv4(127, 0, 0, 1).To4(),
   229  				Mask: net.IPv4Mask(255, 255, 255, 255),
   230  			},
   231  		},
   232  		{
   233  			name: "IPv6",
   234  			addr: mustIP("2001:db8::"),
   235  			want: &net.IPNet{
   236  				IP:   net.ParseIP("2001:db8::"),
   237  				Mask: net.CIDRMask(128, 128),
   238  			},
   239  		},
   240  	}
   241  	for _, tt := range tests {
   242  		t.Run(tt.name, func(t *testing.T) {
   243  			got := AddrIPNet(tt.addr)
   244  			if got == nil {
   245  				t.Fatalf("nil result")
   246  			} else if !reflect.DeepEqual(got, tt.want) {
   247  				t.Errorf("AddrIPNet(%q) = %+v; want %+v", tt.addr, got, tt.want)
   248  			}
   249  		})
   250  	}
   251  }
   252  
   253  type appendMarshaler interface {
   254  	encoding.TextMarshaler
   255  	AppendTo([]byte) []byte
   256  }
   257  
   258  // testAppendToMarshal tests that x's AppendTo and MarshalText methods yield the same results.
   259  // x's MarshalText method must not return an error.
   260  func testAppendToMarshal(t *testing.T, x appendMarshaler) {
   261  	t.Helper()
   262  	m, err := x.MarshalText()
   263  	if err != nil {
   264  		t.Fatalf("(%v).MarshalText: %v", x, err)
   265  	}
   266  	a := make([]byte, 0, len(m))
   267  	a = x.AppendTo(a)
   268  	if !bytes.Equal(m, a) {
   269  		t.Errorf("(%v).MarshalText = %q, (%v).AppendTo = %q", x, m, x, a)
   270  	}
   271  }
   272  
   273  var (
   274  	mustIP       = netip.MustParseAddr
   275  	mustIPPrefix = netip.MustParsePrefix
   276  )
   277  
   278  func mustIPs(strs ...string) []IP {
   279  	var res []IP
   280  	for _, s := range strs {
   281  		res = append(res, mustIP(s))
   282  	}
   283  	return res
   284  }
   285  
   286  func BenchmarkBinaryMarshalRoundTrip(b *testing.B) {
   287  	b.ReportAllocs()
   288  	tests := []struct {
   289  		name string
   290  		ip   string
   291  	}{
   292  		{"ipv4", "1.2.3.4"},
   293  		{"ipv6", "2001:db8::1"},
   294  		{"ipv6+zone", "2001:db8::1%eth0"},
   295  	}
   296  	for _, tc := range tests {
   297  		b.Run(tc.name, func(b *testing.B) {
   298  			ip := mustIP(tc.ip)
   299  			for i := 0; i < b.N; i++ {
   300  				bt, err := ip.MarshalBinary()
   301  				if err != nil {
   302  					b.Fatal(err)
   303  				}
   304  				var ip2 IP
   305  				if err := ip2.UnmarshalBinary(bt); err != nil {
   306  					b.Fatal(err)
   307  				}
   308  			}
   309  		})
   310  	}
   311  }
   312  
   313  func BenchmarkStdIPv4(b *testing.B) {
   314  	b.ReportAllocs()
   315  	ips := []net.IP{}
   316  	for i := 0; i < b.N; i++ {
   317  		ip := net.IPv4(8, 8, 8, 8)
   318  		ips = ips[:0]
   319  		for i := 0; i < 100; i++ {
   320  			ips = append(ips, ip)
   321  		}
   322  	}
   323  }
   324  
   325  func BenchmarkIPv4(b *testing.B) {
   326  	b.ReportAllocs()
   327  	ips := []IP{}
   328  	for i := 0; i < b.N; i++ {
   329  		ip := IPv4(8, 8, 8, 8)
   330  		ips = ips[:0]
   331  		for i := 0; i < 100; i++ {
   332  			ips = append(ips, ip)
   333  		}
   334  	}
   335  }
   336  
   337  // ip4i was one of the possible representations of IP that came up in
   338  // discussions, inlining IPv4 addresses, but having an "overflow"
   339  // interface for IPv6 or IPv6 + zone. This is here for benchmarking.
   340  type ip4i struct {
   341  	ip4    [4]byte
   342  	flags1 byte
   343  	flags2 byte
   344  	flags3 byte
   345  	flags4 byte
   346  	ipv6   interface{}
   347  }
   348  
   349  func newip4i_v4(a, b, c, d byte) ip4i {
   350  	return ip4i{ip4: [4]byte{a, b, c, d}}
   351  }
   352  
   353  // BenchmarkIPv4_inline benchmarks the candidate representation, ip4i.
   354  func BenchmarkIPv4_inline(b *testing.B) {
   355  	b.ReportAllocs()
   356  	ips := []ip4i{}
   357  	for i := 0; i < b.N; i++ {
   358  		ip := newip4i_v4(8, 8, 8, 8)
   359  		ips = ips[:0]
   360  		for i := 0; i < 100; i++ {
   361  			ips = append(ips, ip)
   362  		}
   363  	}
   364  }
   365  
   366  func BenchmarkStdIPv6(b *testing.B) {
   367  	b.ReportAllocs()
   368  	ips := []net.IP{}
   369  	for i := 0; i < b.N; i++ {
   370  		ip := net.ParseIP("2001:db8::1")
   371  		ips = ips[:0]
   372  		for i := 0; i < 100; i++ {
   373  			ips = append(ips, ip)
   374  		}
   375  	}
   376  }
   377  
   378  func BenchmarkIPv6(b *testing.B) {
   379  	b.ReportAllocs()
   380  	ips := []IP{}
   381  	for i := 0; i < b.N; i++ {
   382  		ip := mustIP("2001:db8::1")
   383  		ips = ips[:0]
   384  		for i := 0; i < 100; i++ {
   385  			ips = append(ips, ip)
   386  		}
   387  	}
   388  }
   389  
   390  var parseBenchInputs = []struct {
   391  	name string
   392  	ip   string
   393  }{
   394  	{"v4", "192.168.1.1"},
   395  	{"v6", "fd7a:115c:a1e0:ab12:4843:cd96:626b:430b"},
   396  	{"v6_ellipsis", "fd7a:115c::626b:430b"},
   397  	{"v6_v4", "::ffff:192.168.140.255"},
   398  	{"v6_zone", "1:2::ffff:192.168.140.255%eth1"},
   399  }
   400  
   401  func pxv(cidrStrs ...string) (out []IPPrefix) {
   402  	for _, s := range cidrStrs {
   403  		out = append(out, mustIPPrefix(s))
   404  	}
   405  	return
   406  }
   407  
   408  func TestRangePrefixes(t *testing.T) {
   409  	tests := []struct {
   410  		from string
   411  		to   string
   412  		want []IPPrefix
   413  	}{
   414  		{"0.0.0.0", "255.255.255.255", pxv("0.0.0.0/0")},
   415  		{"::", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", pxv("::/0")},
   416  		{"10.0.0.0", "10.255.255.255", pxv("10.0.0.0/8")},
   417  		{"10.0.0.0", "10.127.255.255", pxv("10.0.0.0/9")},
   418  		{"0.0.0.4", "0.0.0.11", pxv(
   419  			// 4 0100
   420  			// 5 0101
   421  			// 6 0110
   422  			// 7 0111
   423  			// 8 1000
   424  			// 9 1001
   425  			//10 1010
   426  			//11 1011
   427  			"0.0.0.4/30",
   428  			"0.0.0.8/30",
   429  		)},
   430  		{"10.0.0.0", "11.10.255.255", pxv(
   431  			"10.0.0.0/8",
   432  			"11.0.0.0/13",
   433  			"11.8.0.0/15",
   434  			"11.10.0.0/16",
   435  		)},
   436  		{"1.2.3.5", "5.6.7.8", pxv(
   437  			"1.2.3.5/32",
   438  			"1.2.3.6/31",
   439  			"1.2.3.8/29",
   440  			"1.2.3.16/28",
   441  			"1.2.3.32/27",
   442  			"1.2.3.64/26",
   443  			"1.2.3.128/25",
   444  			"1.2.4.0/22",
   445  			"1.2.8.0/21",
   446  			"1.2.16.0/20",
   447  			"1.2.32.0/19",
   448  			"1.2.64.0/18",
   449  			"1.2.128.0/17",
   450  			"1.3.0.0/16",
   451  			"1.4.0.0/14",
   452  			"1.8.0.0/13",
   453  			"1.16.0.0/12",
   454  			"1.32.0.0/11",
   455  			"1.64.0.0/10",
   456  			"1.128.0.0/9",
   457  			"2.0.0.0/7",
   458  			"4.0.0.0/8",
   459  			"5.0.0.0/14",
   460  			"5.4.0.0/15",
   461  			"5.6.0.0/22",
   462  			"5.6.4.0/23",
   463  			"5.6.6.0/24",
   464  			"5.6.7.0/29",
   465  			"5.6.7.8/32",
   466  		)},
   467  	}
   468  	for _, tt := range tests {
   469  		r := IPRangeFrom(mustIP(tt.from), mustIP(tt.to))
   470  		got := r.Prefixes()
   471  		if !reflect.DeepEqual(got, tt.want) {
   472  			t.Errorf("failed %s->%s. got:", tt.from, tt.to)
   473  			for _, v := range got {
   474  				t.Errorf("  %v", v)
   475  			}
   476  			t.Error("want:\n")
   477  			for _, v := range tt.want {
   478  				t.Errorf("  %v", v)
   479  			}
   480  		}
   481  	}
   482  }
   483  
   484  func BenchmarkIPRangePrefixes(b *testing.B) {
   485  	b.ReportAllocs()
   486  	buf := make([]IPPrefix, 0, 50)
   487  	r := IPRange{mustIP("1.2.3.5"), mustIP("5.6.7.8")}
   488  	for i := 0; i < b.N; i++ {
   489  		_ = r.AppendPrefixes(buf[:0])
   490  	}
   491  }
   492  
   493  func TestParseIPRange(t *testing.T) {
   494  	tests := []struct {
   495  		in   string
   496  		want interface{}
   497  	}{
   498  		{"", "no hyphen in range \"\""},
   499  		{"foo-", `invalid From IP "foo" in range "foo-"`},
   500  		{"1.2.3.4-foo", `invalid To IP "foo" in range "1.2.3.4-foo"`},
   501  		{"1.2.3.4-5.6.7.8", IPRange{mustIP("1.2.3.4"), mustIP("5.6.7.8")}},
   502  		{"1.2.3.4-0.1.2.3", "range 1.2.3.4 to 0.1.2.3 not valid"},
   503  		{"::1-::5", IPRange{mustIP("::1"), mustIP("::5")}},
   504  	}
   505  	for _, tt := range tests {
   506  		r, err := ParseIPRange(tt.in)
   507  		var got interface{}
   508  		if err != nil {
   509  			got = err.Error()
   510  		} else {
   511  			got = r
   512  		}
   513  		if got != tt.want {
   514  			t.Errorf("ParseIPRange(%q) = %v; want %v", tt.in, got, tt.want)
   515  		}
   516  		if err == nil {
   517  			back := r.String()
   518  			if back != tt.in {
   519  				t.Errorf("input %q stringifies back as %q", tt.in, back)
   520  			}
   521  		}
   522  
   523  		var r2 IPRange
   524  		err = r2.UnmarshalText([]byte(tt.in))
   525  		if err != nil {
   526  			got = err.Error()
   527  		} else {
   528  			got = r2
   529  		}
   530  		if got != tt.want && tt.in != "" {
   531  			t.Errorf("UnmarshalText(%q) = %v; want %v", tt.in, got, tt.want)
   532  		}
   533  
   534  		testAppendToMarshal(t, r)
   535  	}
   536  }
   537  
   538  func TestIPRangeUnmarshalTextNonZero(t *testing.T) {
   539  	r := MustParseIPRange("1.2.3.4-5.6.7.8")
   540  	if err := r.UnmarshalText([]byte("1.2.3.4-5.6.7.8")); err == nil {
   541  		t.Fatal("unmarshaled into non-empty IPPrefix")
   542  	}
   543  }
   544  
   545  func TestIPRangeContains(t *testing.T) {
   546  	type rtest struct {
   547  		ip   IP
   548  		want bool
   549  	}
   550  	tests := []struct {
   551  		r      IPRange
   552  		rtests []rtest
   553  	}{
   554  		{
   555  			IPRangeFrom(mustIP("10.0.0.2"), mustIP("10.0.0.4")),
   556  			[]rtest{
   557  				{mustIP("10.0.0.1"), false},
   558  				{mustIP("10.0.0.2"), true},
   559  				{mustIP("10.0.0.3"), true},
   560  				{mustIP("10.0.0.4"), true},
   561  				{mustIP("10.0.0.5"), false},
   562  				{IP{}, false},
   563  				{mustIP("::"), false},
   564  			},
   565  		},
   566  		{
   567  			IPRangeFrom(mustIP("::1"), mustIP("::ffff")),
   568  			[]rtest{
   569  				{mustIP("::0"), false},
   570  				{mustIP("::1"), true},
   571  				{mustIP("::1%z"), false},
   572  				{mustIP("::ffff"), true},
   573  				{mustIP("1::"), false},
   574  				{mustIP("0.0.0.1"), false},
   575  				{IP{}, false},
   576  			},
   577  		},
   578  		{
   579  			IPRangeFrom(mustIP("10.0.0.2"), mustIP("::")), // invalid
   580  			[]rtest{
   581  				{mustIP("10.0.0.2"), false},
   582  			},
   583  		},
   584  		{
   585  			IPRange{},
   586  			[]rtest{
   587  				{IP{}, false},
   588  			},
   589  		},
   590  	}
   591  	for _, tt := range tests {
   592  		for _, rt := range tt.rtests {
   593  			got := tt.r.Contains(rt.ip)
   594  			if got != rt.want {
   595  				t.Errorf("Range(%v).Contains(%v) = %v; want %v", tt.r, rt.ip, got, rt.want)
   596  			}
   597  		}
   598  	}
   599  }
   600  
   601  func TestIPRangeOverlaps(t *testing.T) {
   602  	tests := []struct {
   603  		r, o IPRange
   604  		want bool
   605  	}{
   606  		{
   607  			IPRange{},
   608  			IPRange{},
   609  			false,
   610  		},
   611  		{
   612  			IPRange{mustIP("10.0.0.1"), mustIP("10.0.0.3")},
   613  			IPRange{mustIP("10.0.0.3"), mustIP("10.0.0.4")},
   614  			true, // overlaps on edge
   615  		},
   616  		{
   617  			IPRange{mustIP("10.0.0.1"), mustIP("10.0.0.3")},
   618  			IPRange{mustIP("10.0.0.2"), mustIP("10.0.0.4")},
   619  			true, // overlaps in middle
   620  		},
   621  		{
   622  			IPRange{mustIP("10.0.0.1"), mustIP("10.0.0.3")},
   623  			IPRange{mustIP("10.0.0.4"), mustIP("10.0.0.4")},
   624  			false, // doesn't overlap
   625  		},
   626  		{
   627  			IPRange{mustIP("10.0.0.1"), mustIP("10.0.0.3")},
   628  			IPRange{mustIP("10.0.0.0"), mustIP("10.0.0.5")},
   629  			true, // one fully inside the other
   630  		},
   631  		{
   632  			IPRange{mustIP("10.0.0.1"), mustIP("10.0.0.3")},
   633  			IPRange{mustIP("::1"), mustIP("::2")},
   634  			false,
   635  		},
   636  		{
   637  			IPRange{mustIP("::"), mustIP("ff::")},
   638  			IPRange{mustIP("cc::1"), mustIP("cc::2")},
   639  			true,
   640  		},
   641  	}
   642  	for _, tt := range tests {
   643  		got := tt.r.Overlaps(tt.o)
   644  		if got != tt.want {
   645  			t.Errorf("Overlaps(%v, %v) = %v; want %v", tt.r, tt.o, got, tt.want)
   646  		}
   647  		got = tt.o.Overlaps(tt.r)
   648  		if got != tt.want {
   649  			t.Errorf("Overlaps(%v, %v) (reversed) = %v; want %v", tt.o, tt.r, got, tt.want)
   650  		}
   651  	}
   652  }
   653  
   654  func TestIPRangeValid(t *testing.T) {
   655  	tests := []struct {
   656  		r    IPRange
   657  		want bool
   658  	}{
   659  		{IPRange{mustIP("10.0.0.0"), mustIP("10.0.0.255")}, true},
   660  		{IPRange{mustIP("::1"), mustIP("::2")}, true},
   661  		{IPRange{mustIP("::1%foo"), mustIP("::2%foo")}, true},
   662  
   663  		{IPRange{mustIP("::1%foo"), mustIP("::2%bar")}, false}, // zones differ
   664  		{IPRange{IP{}, IP{}}, false},                           // zero values
   665  		{IPRange{mustIP("::2"), mustIP("::1")}, false},         // bad order
   666  		{IPRange{mustIP("1.2.3.4"), mustIP("::1")}, false},     // family mismatch
   667  	}
   668  	for _, tt := range tests {
   669  		got := tt.r.IsValid()
   670  		if got != tt.want {
   671  			t.Errorf("range %v to %v Valid = %v; want %v", tt.r.From(), tt.r.To(), got, tt.want)
   672  		}
   673  	}
   674  }
   675  
   676  func TestIPRangePrefix(t *testing.T) {
   677  	tests := []struct {
   678  		r    IPRange
   679  		want IPPrefix
   680  	}{
   681  		{IPRange{mustIP("10.0.0.0"), mustIP("10.0.0.255")}, mustIPPrefix("10.0.0.0/24")},
   682  		{IPRange{mustIP("10.0.0.0"), mustIP("10.0.0.254")}, IPPrefix{}},
   683  		{IPRange{mustIP("fc00::"), AddrPrior(mustIP("fe00::"))}, mustIPPrefix("fc00::/7")},
   684  	}
   685  	for _, tt := range tests {
   686  		got, ok := tt.r.Prefix()
   687  		if ok != (got != IPPrefix{}) {
   688  			t.Errorf("for %v, Prefix() results inconsistent: %v, %v", tt.r, got, ok)
   689  		}
   690  		if got != tt.want {
   691  			t.Errorf("for %v, Prefix = %v; want %v", tt.r, got, tt.want)
   692  		}
   693  	}
   694  
   695  	allocs := int(testing.AllocsPerRun(1000, func() {
   696  		tt := tests[0]
   697  		if _, ok := tt.r.Prefix(); !ok {
   698  			t.Fatal("expected okay")
   699  		}
   700  	}))
   701  	if allocs != 0 {
   702  		t.Errorf("allocs = %v", allocs)
   703  	}
   704  }
   705  
   706  func BenchmarkIPRangePrefix(b *testing.B) {
   707  	b.ReportAllocs()
   708  	r := IPRange{mustIP("10.0.0.0"), mustIP("10.0.0.255")}
   709  	for i := 0; i < b.N; i++ {
   710  		if _, ok := r.Prefix(); !ok {
   711  			b.Fatal("expected a prefix")
   712  		}
   713  	}
   714  }
   715  
   716  var nextPriorTests = []struct {
   717  	ip    IP
   718  	next  IP
   719  	prior IP
   720  }{
   721  	{mustIP("10.0.0.1"), mustIP("10.0.0.2"), mustIP("10.0.0.0")},
   722  	{mustIP("10.0.0.255"), mustIP("10.0.1.0"), mustIP("10.0.0.254")},
   723  	{mustIP("127.0.0.1"), mustIP("127.0.0.2"), mustIP("127.0.0.0")},
   724  	{mustIP("254.255.255.255"), mustIP("255.0.0.0"), mustIP("254.255.255.254")},
   725  	{mustIP("255.255.255.255"), IP{}, mustIP("255.255.255.254")},
   726  	{mustIP("0.0.0.0"), mustIP("0.0.0.1"), IP{}},
   727  	{mustIP("::"), mustIP("::1"), IP{}},
   728  	{mustIP("::%x"), mustIP("::1%x"), IP{}},
   729  	{mustIP("::1"), mustIP("::2"), mustIP("::")},
   730  	{mustIP("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), IP{}, mustIP("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe")},
   731  }
   732  
   733  func TestAddrNextPrior(t *testing.T) {
   734  	doNextPrior(t)
   735  
   736  	for _, ip := range []IP{
   737  		mustIP("0.0.0.0"),
   738  		mustIP("::"),
   739  	} {
   740  		got := AddrPrior(ip)
   741  		if got.IsValid() {
   742  			t.Errorf("IP(%v).Prior = %v; want zero", ip, got)
   743  		}
   744  	}
   745  
   746  	var allFF [16]byte
   747  	for i := range allFF {
   748  		allFF[i] = 0xff
   749  	}
   750  
   751  	for _, ip := range []IP{
   752  		mustIP("255.255.255.255"),
   753  		netip.AddrFrom16(allFF),
   754  	} {
   755  		got := ip.Next()
   756  		if got.IsValid() {
   757  			t.Errorf("IP(%v).Next = %v; want zero", ip, got)
   758  		}
   759  	}
   760  }
   761  
   762  func BenchmarkIPNextPrior(b *testing.B) {
   763  	for i := 0; i < b.N; i++ {
   764  		doNextPrior(b)
   765  	}
   766  }
   767  
   768  func doNextPrior(t testing.TB) {
   769  	for _, tt := range nextPriorTests {
   770  		gnext, gprior := AddrNext(tt.ip), AddrPrior(tt.ip)
   771  		if gnext != tt.next {
   772  			t.Errorf("IP(%v).Next = %v; want %v", tt.ip, gnext, tt.next)
   773  		}
   774  		if gprior != tt.prior {
   775  			t.Errorf("IP(%v).Prior = %v; want %v", tt.ip, gprior, tt.prior)
   776  		}
   777  		if AddrNext(tt.ip).IsValid() && AddrPrior(AddrNext(tt.ip)) != tt.ip {
   778  			t.Errorf("IP(%v).Next.Prior = %v; want %v", tt.ip, AddrPrior(AddrNext(tt.ip)), tt.ip)
   779  		}
   780  		if AddrPrior(tt.ip).IsValid() && AddrNext(AddrPrior(tt.ip)) != tt.ip {
   781  			t.Errorf("IP(%v).Prior.Next = %v; want %v", tt.ip, AddrNext(AddrPrior(tt.ip)), tt.ip)
   782  		}
   783  	}
   784  }
   785  
   786  // Sink variables are here to force the compiler to not elide
   787  // seemingly useless work in benchmarks and allocation tests. If you
   788  // were to just `_ = foo()` within a test function, the compiler could
   789  // correctly deduce that foo() does nothing and doesn't need to be
   790  // called. By writing results to a global variable, we hide that fact
   791  // from the compiler and force it to keep the code under test.
   792  var (
   793  	sinkIP            IP
   794  	sinkStdIP         net.IP
   795  	sinkIPPort        IPPort
   796  	sinkIPPrefix      IPPrefix
   797  	sinkIPPrefixSlice []IPPrefix
   798  	sinkIPRange       IPRange
   799  	sinkIP16          [16]byte
   800  	sinkIP4           [4]byte
   801  	sinkBool          bool
   802  	sinkString        string
   803  	sinkBytes         []byte
   804  	sinkUDPAddr       = &net.UDPAddr{IP: make(net.IP, 0, 16)}
   805  )
   806  
   807  func TestNoAllocs(t *testing.T) {
   808  	// Wrappers that panic on error, to prove that our alloc-free
   809  	// methods are returning successfully.
   810  	panicIPOK := func(ip IP, ok bool) IP {
   811  		if !ok {
   812  			panic("not ok")
   813  		}
   814  		return ip
   815  	}
   816  	panicPfxOK := func(pfx IPPrefix, ok bool) IPPrefix {
   817  		if !ok {
   818  			panic("not ok")
   819  		}
   820  		return pfx
   821  	}
   822  	panicIPPOK := func(ipp IPPort, ok bool) IPPort {
   823  		if !ok {
   824  			panic("not ok")
   825  		}
   826  		return ipp
   827  	}
   828  
   829  	test := func(name string, f func()) {
   830  		t.Run(name, func(t *testing.T) {
   831  			n := testing.AllocsPerRun(1000, f)
   832  			if n != 0 {
   833  				t.Fatalf("allocs = %d; want 0", int(n))
   834  			}
   835  		})
   836  	}
   837  
   838  	// IP constructors
   839  	test("FromStdIP", func() { sinkIP = panicIPOK(FromStdIP(net.IP([]byte{1, 2, 3, 4}))) })
   840  	test("FromStdIPRaw", func() { sinkIP = panicIPOK(FromStdIPRaw(net.IP([]byte{1, 2, 3, 4}))) })
   841  
   842  	// IPPort constructors
   843  	test("FromStdAddr", func() {
   844  		std := net.IP{1, 2, 3, 4}
   845  		sinkIPPort = panicIPPOK(FromStdAddr(std, 5678, ""))
   846  	})
   847  
   848  	// IPPrefix constructors
   849  	test("FromStdIPNet", func() {
   850  		std := &net.IPNet{
   851  			IP:   net.IP{1, 2, 3, 4},
   852  			Mask: net.IPMask{255, 255, 0, 0},
   853  		}
   854  		sinkIPPrefix = panicPfxOK(FromStdIPNet(std))
   855  	})
   856  
   857  	// IPRange constructors
   858  	test("IPRangeFrom", func() { sinkIPRange = IPRangeFrom(IPv4(1, 2, 3, 4), IPv4(4, 3, 2, 1)) })
   859  	test("ParseIPRange", func() { sinkIPRange = MustParseIPRange("1.2.3.0-1.2.4.150") })
   860  
   861  	// IPRange methods
   862  	test("IPRange.IsZero", func() { sinkBool = MustParseIPRange("1.2.3.0-1.2.4.150").IsZero() })
   863  	test("IPRange.IsValid", func() { sinkBool = MustParseIPRange("1.2.3.0-1.2.4.150").IsValid() })
   864  	test("IPRange.Overlaps", func() {
   865  		a := MustParseIPRange("1.2.3.0-1.2.3.150")
   866  		b := MustParseIPRange("1.2.4.0-1.2.4.255")
   867  		sinkBool = a.Overlaps(b)
   868  	})
   869  	test("IPRange.Prefix", func() {
   870  		a := MustParseIPRange("1.2.3.0-1.2.3.255")
   871  		sinkIPPrefix = panicPfxOK(a.Prefix())
   872  	})
   873  }
   874  

View as plain text