| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| //go:build !build_with_native_toolchain |
| // +build !build_with_native_toolchain |
| |
| package netstack |
| |
| import ( |
| "context" |
| "fmt" |
| "reflect" |
| "sort" |
| "strconv" |
| "syscall/zx" |
| "testing" |
| "time" |
| |
| "go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/dhcp" |
| ethernetext "go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/fidlext/fuchsia/hardware/ethernet" |
| "go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/link/eth" |
| "go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/routes" |
| "go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/util" |
| |
| "fidl/fuchsia/hardware/ethernet" |
| inspect "fidl/fuchsia/inspect/deprecated" |
| |
| "github.com/google/go-cmp/cmp" |
| "github.com/google/go-cmp/cmp/cmpopts" |
| "go.uber.org/multierr" |
| "gvisor.dev/gvisor/pkg/tcpip" |
| "gvisor.dev/gvisor/pkg/tcpip/header" |
| "gvisor.dev/gvisor/pkg/tcpip/network/arp" |
| "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" |
| "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" |
| "gvisor.dev/gvisor/pkg/tcpip/stack" |
| "gvisor.dev/gvisor/pkg/tcpip/transport/tcp" |
| "gvisor.dev/gvisor/pkg/tcpip/transport/udp" |
| "gvisor.dev/gvisor/pkg/waiter" |
| ) |
| |
| const ( |
| ipv4Addr = tcpip.Address("\xc0\xa8\x01\x01") |
| ipv6Addr = tcpip.Address("\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01") |
| ) |
| |
| type inspectNodeExpectation struct { |
| node inspect.Object |
| children []inspectNodeExpectation |
| } |
| |
| func checkInspectRecurse(node inspectInner, expected inspectNodeExpectation) error { |
| var err error |
| |
| nodeData := node.ReadData() |
| |
| if nodeData.Name != expected.node.Name { |
| err = multierr.Append(err, fmt.Errorf("found unexpected name %s instead of %s", nodeData.Name, expected.node.Name)) |
| } |
| |
| if diff := cmp.Diff(expected.node.Properties, nodeData.Properties, cmpopts.IgnoreUnexported(inspect.Object{}, inspect.Metric{})); diff != "" { |
| err = multierr.Append(err, fmt.Errorf("Properties mismatch (-want +got):\n%s", diff)) |
| } |
| |
| containsMetric := func(metrics []inspect.Metric, metricToFind inspect.Metric) bool { |
| for _, metric := range metrics { |
| if metric == metricToFind { |
| return true |
| } |
| } |
| |
| return false |
| } |
| |
| for _, metric := range nodeData.Metrics { |
| if metric.Value != inspect.MetricValueWithUintValue(0) && !containsMetric(expected.node.Metrics, metric) { |
| err = multierr.Append(err, fmt.Errorf("ReadData() mismatch: found unexpected non-zero metric %#v", metric)) |
| } |
| } |
| |
| for _, metric := range expected.node.Metrics { |
| if !containsMetric(nodeData.Metrics, metric) { |
| err = multierr.Append(err, fmt.Errorf("ReadData() mismatch: missing expected non-zero metric %#v", metric)) |
| } |
| } |
| |
| children := node.ListChildren() |
| |
| var expectedChildrenNames []string |
| for _, child := range expected.children { |
| expectedChildrenNames = append(expectedChildrenNames, child.node.Name) |
| } |
| |
| if diff := cmp.Diff(expectedChildrenNames, children, cmpopts.SortSlices(func(a, b string) bool { |
| return a < b |
| })); diff != "" { |
| err = multierr.Append(err, fmt.Errorf("ListChildren() mismatch (-want +got):\n%s", diff)) |
| } |
| |
| childName := "not a real child" |
| if child := node.GetChild(childName); child != nil { |
| err = multierr.Append(err, fmt.Errorf("got GetChild(%s) = %s, want = nil", childName, child)) |
| } |
| |
| for i, childName := range expectedChildrenNames { |
| if child := node.GetChild(childName); child != nil { |
| err = multierr.Append(err, checkInspectRecurse(child, expected.children[i])) |
| } else { |
| err = multierr.Append(err, fmt.Errorf("got GetChild(%s) = nil, want non-nil", childName)) |
| } |
| } |
| return err |
| } |
| |
| func TestStatCounterInspectImpl(t *testing.T) { |
| const invalidPort = 1 |
| const invalidPortCount = 10 |
| const initAcquireCount = 3 |
| var s dhcp.Stats |
| s.PacketDiscardStats.InvalidPort.Init() |
| s.PacketDiscardStats.InvalidPacketType.Init() |
| s.PacketDiscardStats.InvalidTransProto.Init() |
| for i := 0; i < invalidPortCount; i++ { |
| s.PacketDiscardStats.InvalidPort.Increment(invalidPort) |
| } |
| |
| s.InitAcquire.IncrementBy(initAcquireCount) |
| |
| v := statCounterInspectImpl{ |
| name: "doesn't matter", |
| value: reflect.ValueOf(&s).Elem(), |
| } |
| |
| expected := inspectNodeExpectation{ |
| node: inspect.Object{ |
| Name: "doesn't matter", |
| Metrics: []inspect.Metric{ |
| {Key: "InitAcquire", Value: inspect.MetricValueWithUintValue(initAcquireCount)}, |
| }, |
| }, |
| children: []inspectNodeExpectation{ |
| { |
| node: inspect.Object{ |
| Name: "PacketDiscardStats", |
| }, |
| children: []inspectNodeExpectation{ |
| { |
| node: inspect.Object{ |
| Name: "InvalidPort", |
| }, |
| children: []inspectNodeExpectation{ |
| { |
| node: inspect.Object{ |
| Name: "1", |
| Properties: []inspect.Property{ |
| {Key: "Count", Value: inspect.PropertyValueWithStr("10")}, |
| }, |
| }, |
| }, |
| { |
| node: inspect.Object{ |
| Name: "Total", |
| Properties: []inspect.Property{ |
| {Key: "Count", Value: inspect.PropertyValueWithStr("10")}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| { |
| node: inspect.Object{ |
| Name: "InvalidTransProto", |
| }, |
| children: []inspectNodeExpectation{ |
| { |
| node: inspect.Object{ |
| Name: "Total", |
| Properties: []inspect.Property{ |
| {Key: "Count", Value: inspect.PropertyValueWithStr("0")}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| { |
| node: inspect.Object{ |
| Name: "InvalidPacketType", |
| }, |
| children: []inspectNodeExpectation{ |
| { |
| node: inspect.Object{ |
| Name: "Total", |
| Properties: []inspect.Property{ |
| {Key: "Count", Value: inspect.PropertyValueWithStr("0")}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| } |
| |
| if err := checkInspectRecurse(&v, expected); err != nil { |
| t.Error(err) |
| } |
| } |
| |
| func circularLogsChecker(logs *circularLogsInspectImpl, expectedChildren []string, expectedChildrenData []inspect.Object) []error { |
| var errors []error |
| |
| if diff := cmp.Diff(inspect.Object{ |
| Name: logs.name, |
| }, logs.ReadData(), cmpopts.IgnoreUnexported(inspect.Object{}, inspect.Metric{})); diff != "" { |
| errors = append(errors, fmt.Errorf("ReadData() mismatch (-want +got):\n%s", diff)) |
| } |
| |
| children := logs.ListChildren() |
| if diff := cmp.Diff(expectedChildren, children); diff != "" { |
| errors = append(errors, fmt.Errorf("ListChildren() mismatch (-want +got):\n%s", diff)) |
| return errors |
| } |
| |
| childName := "not a real child" |
| if child := logs.GetChild(childName); child != nil { |
| errors = append(errors, fmt.Errorf("got GetChild(%s) = %s, want = nil", childName, child)) |
| } |
| |
| for i, childName := range children { |
| if child := logs.GetChild(childName); child == nil { |
| errors = append(errors, fmt.Errorf("got GetChild(%s) = nil, want non-nil", childName)) |
| } else if entry, ok := child.(*logEntryInspectImpl); !ok { |
| errors = append(errors, fmt.Errorf("got GetChild(%s) = %#v, want %T", childName, child, (*statCounterInspectImpl)(nil))) |
| } else { |
| if diff := cmp.Diff(expectedChildrenData[i], entry.ReadData(), cmpopts.IgnoreUnexported( |
| inspect.Object{}, inspect.Metric{})); diff != "" { |
| errors = append(errors, fmt.Errorf("ReadData() mismatch (-want +got):\n%s", diff)) |
| } |
| if diff := cmp.Diff(child.ListChildren(), []string(nil)); diff != "" { |
| errors = append(errors, fmt.Errorf("ListChildren() mismatch (-want +got):\n%s", diff)) |
| } |
| } |
| } |
| |
| return errors |
| } |
| |
| func TestCircularLogsInspectImpl(t *testing.T) { |
| v := circularLogsInspectImpl{ |
| name: "dosn't matter", |
| value: []util.LogEntry{ |
| {Timestamp: 42, Content: "foo"}, |
| {Timestamp: 1337, Content: "bar"}, |
| }, |
| } |
| |
| expectedChildren := []string{"0", "1"} |
| expectedChildrenData := []inspect.Object{ |
| { |
| Name: "0", |
| Properties: []inspect.Property{ |
| {Key: "@time", Value: inspect.PropertyValueWithStr("42")}, |
| {Key: "value", Value: inspect.PropertyValueWithStr("foo")}, |
| }, |
| }, |
| { |
| Name: "1", |
| Properties: []inspect.Property{ |
| {Key: "@time", Value: inspect.PropertyValueWithStr("1337")}, |
| {Key: "value", Value: inspect.PropertyValueWithStr("bar")}, |
| }, |
| }, |
| } |
| |
| errors := circularLogsChecker(&v, expectedChildren, expectedChildrenData) |
| for _, err := range errors { |
| t.Error(err) |
| } |
| } |
| |
| func TestIntegralStatCounterMapInspectImpl(t *testing.T) { |
| var integralStatCounterMap tcpip.IntegralStatCounterMap |
| integralStatCounterMap.Init() |
| const firstKey = 1 |
| const firstValue = 10 |
| for i := 0; i < firstValue; i++ { |
| integralStatCounterMap.Increment(firstKey) |
| } |
| |
| const secondKey = 2 |
| const secondValue = 20 |
| for i := 0; i < secondValue; i++ { |
| integralStatCounterMap.Increment(secondKey) |
| } |
| v := integralStatCounterMapInspectImpl{ |
| name: "doesn't matter", |
| value: &integralStatCounterMap, |
| } |
| |
| expected := inspectNodeExpectation{ |
| node: inspect.Object{ |
| Name: "doesn't matter", |
| }, |
| children: []inspectNodeExpectation{ |
| { |
| node: inspect.Object{ |
| Name: strconv.Itoa(firstKey), |
| Properties: []inspect.Property{ |
| {Key: "Count", Value: inspect.PropertyValueWithStr("10")}, |
| }, |
| }, |
| }, |
| { |
| node: inspect.Object{ |
| Name: strconv.Itoa(secondKey), |
| Properties: []inspect.Property{ |
| {Key: "Count", Value: inspect.PropertyValueWithStr("20")}, |
| }, |
| }, |
| }, |
| { |
| node: inspect.Object{ |
| Name: "Total", |
| Properties: []inspect.Property{ |
| {Key: "Count", Value: inspect.PropertyValueWithStr("30")}, |
| }, |
| }, |
| }, |
| }, |
| } |
| |
| if err := checkInspectRecurse(&v, expected); err != nil { |
| t.Error(err) |
| } |
| } |
| |
| func TestSocketStatCounterInspectImpl(t *testing.T) { |
| // Create a new netstack and add TCP and UDP endpoints. |
| ns, _ := newNetstack(t) |
| wq := new(waiter.Queue) |
| tcpEP, err := ns.stack.NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, wq) |
| if err != nil { |
| t.Fatal(err) |
| } |
| udpEP, err := ns.stack.NewEndpoint(udp.ProtocolNumber, ipv6.ProtocolNumber, wq) |
| if err != nil { |
| t.Fatal(err) |
| } |
| v := socketInfoMapInspectImpl{ |
| value: &ns.endpoints, |
| } |
| children := v.ListChildren() |
| if diff := cmp.Diff([]string(nil), children); diff != "" { |
| t.Errorf("ListChildren() mismatch (-want +got):\n%s", diff) |
| } |
| |
| // Add the 2 endpoints to endpoints with key being the transport protocol |
| // number. |
| const key1 = 1 |
| const key2 = 2 |
| ns.endpoints.Store(key1, tcpEP) |
| ns.endpoints.Store(key2, udpEP) |
| |
| children = v.ListChildren() |
| sort.Strings(children) |
| if diff := cmp.Diff([]string{strconv.Itoa(key1), strconv.Itoa(key2)}, children); diff != "" { |
| t.Errorf("ListChildren() mismatch (-want +got):\n%s", diff) |
| } |
| |
| childName := "not a real child" |
| if child := v.GetChild(childName); child != nil { |
| t.Errorf("got GetChild(%s) = %s, want = nil", childName, child) |
| } |
| |
| for _, name := range children { |
| child := v.GetChild(name) |
| if child == nil { |
| t.Fatalf("got GetChild(%s) = nil, want non-nil", name) |
| } |
| |
| val, err := strconv.ParseUint(name, 10, 64) |
| if err != nil { |
| t.Fatalf("string parsing error %s", err) |
| } |
| |
| // Update protocol specific expected values. |
| var ( |
| networkProtoName, transportProtoName string |
| unspecifiedAddress string |
| expectInspectObj inspect.Object |
| ) |
| switch val { |
| case key1: |
| networkProtoName = "IPv4" |
| transportProtoName = "TCP" |
| unspecifiedAddress = "0.0.0.0" |
| expectInspectObj = inspect.Object{ |
| Name: "Stats", |
| Metrics: []inspect.Metric{ |
| {Key: "SegmentsReceived", Value: inspect.MetricValueWithUintValue(0)}, |
| {Key: "SegmentsSent", Value: inspect.MetricValueWithUintValue(0)}, |
| {Key: "FailedConnectionAttempts", Value: inspect.MetricValueWithUintValue(0)}, |
| }, |
| } |
| case key2: |
| networkProtoName = "IPv6" |
| transportProtoName = "UDP" |
| unspecifiedAddress = "[::]" |
| expectInspectObj = inspect.Object{ |
| Name: "Stats", |
| Metrics: []inspect.Metric{ |
| {Key: "PacketsReceived", Value: inspect.MetricValueWithUintValue(0)}, |
| {Key: "PacketsSent", Value: inspect.MetricValueWithUintValue(0)}, |
| }, |
| } |
| } |
| |
| epChildren := child.ListChildren() |
| if diff := cmp.Diff([]string{ |
| "Stats", |
| }, epChildren); diff != "" { |
| t.Errorf("ListChildren() mismatch (-want +got):\n%s", diff) |
| } |
| for _, c := range epChildren { |
| if c == "Stats" { |
| statsChild := child.GetChild(c) |
| if diff := cmp.Diff([]string{ |
| "ReceiveErrors", |
| "ReadErrors", |
| "SendErrors", |
| "WriteErrors", |
| }, statsChild.ListChildren()); diff != "" { |
| t.Errorf("ListChildren() mismatch (-want +got):\n%s", diff) |
| } |
| |
| // Compare against the expected inspect objects. |
| if diff := cmp.Diff(expectInspectObj, statsChild.ReadData(), cmpopts.IgnoreUnexported(inspect.Object{}, inspect.Metric{}, inspect.Property{})); diff != "" { |
| t.Errorf("ReadData() mismatch (-want +got):\n%s", diff) |
| } |
| } |
| } |
| |
| if diff := cmp.Diff(inspect.Object{ |
| Name: name, |
| Properties: []inspect.Property{ |
| {Key: "NetworkProtocol", Value: inspect.PropertyValueWithStr(networkProtoName)}, |
| {Key: "TransportProtocol", Value: inspect.PropertyValueWithStr(transportProtoName)}, |
| {Key: "State", Value: inspect.PropertyValueWithStr("INITIAL")}, |
| {Key: "LocalAddress", Value: inspect.PropertyValueWithStr(unspecifiedAddress + ":0")}, |
| {Key: "RemoteAddress", Value: inspect.PropertyValueWithStr(unspecifiedAddress + ":0")}, |
| {Key: "BindAddress", Value: inspect.PropertyValueWithStr("")}, |
| {Key: "BindNICID", Value: inspect.PropertyValueWithStr("0")}, |
| {Key: "RegisterNICID", Value: inspect.PropertyValueWithStr("0")}, |
| }, |
| }, child.ReadData(), cmpopts.IgnoreUnexported(inspect.Object{}, inspect.Metric{}, inspect.Property{})); diff != "" { |
| t.Errorf("ReadData() mismatch (-want +got):\n%s", diff) |
| } |
| } |
| |
| // Empty the endpoints. |
| ns.endpoints.Range(func(key uint64, _ tcpip.Endpoint) bool { |
| ns.endpoints.Delete(key) |
| return true |
| }) |
| children = v.ListChildren() |
| if diff := cmp.Diff([]string(nil), children); diff != "" { |
| t.Errorf("ListChildren() mismatch (-want +got):\n%s", diff) |
| } |
| } |
| |
| func TestNicInfoMapInspectImpl(t *testing.T) { |
| v := nicInfoMapInspectImpl{ |
| value: map[tcpip.NICID]ifStateInfo{ |
| 1: {}, |
| 2: {}, |
| }, |
| } |
| children := v.ListChildren() |
| if diff := cmp.Diff([]string{ |
| "1", "2", |
| }, children); diff != "" { |
| t.Errorf("ListChildren() mismatch (-want +got):\n%s", diff) |
| } |
| for _, childName := range children { |
| if child := v.GetChild(childName); child == nil { |
| t.Errorf("got GetChild(%s) = nil, want non-nil", childName) |
| } else if _, ok := child.(*nicInfoInspectImpl); !ok { |
| t.Errorf("got GetChild(%s) = %#v, want %T", childName, child, (*nicInfoInspectImpl)(nil)) |
| } |
| } |
| |
| childName := "not a real child" |
| if child := v.GetChild(childName); child != nil { |
| t.Errorf("got GetChild(%s) = %s, want = nil", childName, child) |
| } |
| |
| if diff := cmp.Diff(inspect.Object{ |
| Name: "NICs", |
| }, v.ReadData(), cmpopts.IgnoreUnexported(inspect.Object{})); diff != "" { |
| t.Errorf("ReadData() mismatch (-want +got):\n%s", diff) |
| } |
| } |
| |
| func TestNicInfoInspectImpl(t *testing.T) { |
| v := nicInfoInspectImpl{ |
| name: "doesn't matter", |
| } |
| children := v.ListChildren() |
| if diff := cmp.Diff([]string{ |
| "Stats", |
| }, children); diff != "" { |
| t.Errorf("ListChildren() mismatch (-want +got):\n%s", diff) |
| } |
| for _, childName := range children { |
| if child := v.GetChild(childName); child == nil { |
| t.Errorf("got GetChild(%s) = nil, want non-nil", childName) |
| } else if _, ok := child.(*statCounterInspectImpl); !ok { |
| t.Errorf("got GetChild(%s) = %#v, want %T", childName, child, (*statCounterInspectImpl)(nil)) |
| } |
| } |
| |
| childName := "not a real child" |
| if child := v.GetChild(childName); child != nil { |
| t.Errorf("got GetChild(%s) = %s, want = nil", childName, child) |
| } |
| |
| v.value.nicid = 5 |
| v.value.Flags.Up = true |
| v.value.Flags.Loopback = true |
| v.value.dnsServers = []tcpip.Address{ipv4Addr} |
| |
| if diff := cmp.Diff(inspect.Object{ |
| Name: v.name, |
| Properties: []inspect.Property{ |
| {Key: "Name", Value: inspect.PropertyValueWithStr(v.value.Name)}, |
| {Key: "NICID", Value: inspect.PropertyValueWithStr("5")}, |
| {Key: "AdminUp", Value: inspect.PropertyValueWithStr("false")}, |
| {Key: "LinkOnline", Value: inspect.PropertyValueWithStr("false")}, |
| {Key: "Up", Value: inspect.PropertyValueWithStr("true")}, |
| {Key: "Running", Value: inspect.PropertyValueWithStr("false")}, |
| {Key: "Loopback", Value: inspect.PropertyValueWithStr("true")}, |
| {Key: "Promiscuous", Value: inspect.PropertyValueWithStr("false")}, |
| {Key: "DNS server0", Value: inspect.PropertyValueWithStr(ipv4Addr.String())}, |
| {Key: "DHCP enabled", Value: inspect.PropertyValueWithStr("false")}, |
| }, |
| Metrics: []inspect.Metric{ |
| {Key: "MTU", Value: inspect.MetricValueWithUintValue(uint64(v.value.MTU))}, |
| }, |
| }, v.ReadData(), cmpopts.IgnoreUnexported(inspect.Object{}, inspect.Property{}, inspect.Metric{})); diff != "" { |
| t.Errorf("ReadData() mismatch (-want +got):\n%s", diff) |
| } |
| } |
| |
| func TestDHCPInfoInspectImpl(t *testing.T) { |
| var invalidPortCounter, invalidTransProtoCounter, invalidPacketTypeCounter, invalidPacketType2Counter tcpip.StatCounter |
| |
| const invalidPort = 1 |
| const invalidPortCount = 10 |
| invalidPortCounter.IncrementBy(invalidPortCount) |
| |
| const invalidTransProto = 2 |
| const invalidTransProtoCount = 20 |
| invalidTransProtoCounter.IncrementBy(invalidTransProtoCount) |
| |
| const invalidPacketType = 3 |
| const invalidPacketTypeCount = 30 |
| invalidPacketTypeCounter.IncrementBy(invalidPacketTypeCount) |
| |
| const invalidPacketType2 = 4 |
| const invalidPacketType2Count = 40 |
| invalidPacketType2Counter.IncrementBy(invalidPacketType2Count) |
| |
| const counterPropertyKey = "Count" |
| |
| v := dhcpInfoInspectImpl{ |
| name: "doesn't matter", |
| stateRecentHistory: []util.LogEntry{ |
| {Timestamp: 1, Content: "1"}, |
| {Timestamp: 2, Content: "2"}, |
| }, |
| stats: &dhcp.Stats{}, |
| } |
| v.stats.PacketDiscardStats.InvalidPort.Init() |
| v.stats.PacketDiscardStats.InvalidTransProto.Init() |
| v.stats.PacketDiscardStats.InvalidPacketType.Init() |
| for i := 0; i < invalidPortCount; i++ { |
| v.stats.PacketDiscardStats.InvalidPort.Increment(invalidPort) |
| } |
| for i := 0; i < invalidTransProtoCount; i++ { |
| v.stats.PacketDiscardStats.InvalidTransProto.Increment(invalidTransProto) |
| } |
| for i := 0; i < invalidPacketTypeCount; i++ { |
| v.stats.PacketDiscardStats.InvalidPacketType.Increment(invalidPacketType) |
| } |
| for i := 0; i < invalidPacketType2Count; i++ { |
| v.stats.PacketDiscardStats.InvalidPacketType.Increment(invalidPacketType2) |
| } |
| children := v.ListChildren() |
| if diff := cmp.Diff([]string{ |
| "Stats", "DHCP State Recent History", |
| }, children); diff != "" { |
| t.Errorf("ListChildren() mismatch (-want +got):\n%s", diff) |
| } |
| |
| { |
| // Validate Stats struct. |
| child := v.GetChild("Stats") |
| stats, ok := child.(*statCounterInspectImpl) |
| |
| if !ok { |
| t.Fatalf("got GetChild(Stats) = %#v, want %T", child, (*statCounterInspectImpl)(nil)) |
| } |
| |
| expected := inspectNodeExpectation{ |
| node: inspect.Object{ |
| Name: "Stats", |
| }, |
| children: []inspectNodeExpectation{ |
| { |
| node: inspect.Object{ |
| Name: "PacketDiscardStats", |
| }, |
| children: []inspectNodeExpectation{ |
| { |
| node: inspect.Object{ |
| Name: "InvalidPort", |
| }, |
| children: []inspectNodeExpectation{ |
| { |
| node: inspect.Object{ |
| Name: strconv.Itoa(invalidPort), |
| Properties: []inspect.Property{ |
| {Key: counterPropertyKey, Value: inspect.PropertyValueWithStr(strconv.FormatUint(invalidPortCounter.Value(), 10))}, |
| }, |
| }, |
| }, |
| { |
| node: inspect.Object{ |
| Name: "Total", |
| Properties: []inspect.Property{ |
| {Key: counterPropertyKey, Value: inspect.PropertyValueWithStr(strconv.FormatUint(invalidPortCounter.Value(), 10))}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| { |
| node: inspect.Object{ |
| Name: "InvalidTransProto", |
| }, |
| children: []inspectNodeExpectation{ |
| { |
| node: inspect.Object{ |
| Name: strconv.Itoa(invalidTransProto), |
| Properties: []inspect.Property{ |
| {Key: counterPropertyKey, Value: inspect.PropertyValueWithStr(strconv.FormatUint(invalidTransProtoCounter.Value(), 10))}, |
| }, |
| }, |
| }, |
| { |
| node: inspect.Object{ |
| Name: "Total", |
| Properties: []inspect.Property{ |
| {Key: counterPropertyKey, Value: inspect.PropertyValueWithStr(strconv.FormatUint(invalidTransProtoCounter.Value(), 10))}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| { |
| node: inspect.Object{ |
| Name: "InvalidPacketType", |
| }, |
| children: []inspectNodeExpectation{ |
| { |
| node: inspect.Object{ |
| Name: strconv.Itoa(invalidPacketType), |
| Properties: []inspect.Property{ |
| {Key: counterPropertyKey, Value: inspect.PropertyValueWithStr(strconv.FormatUint(invalidPacketTypeCounter.Value(), 10))}, |
| }, |
| }, |
| }, |
| { |
| node: inspect.Object{ |
| Name: strconv.Itoa(invalidPacketType2), |
| Properties: []inspect.Property{ |
| {Key: counterPropertyKey, Value: inspect.PropertyValueWithStr(strconv.FormatUint(invalidPacketType2Counter.Value(), 10))}, |
| }, |
| }, |
| }, |
| { |
| node: inspect.Object{ |
| Name: "Total", |
| Properties: []inspect.Property{ |
| {Key: counterPropertyKey, Value: inspect.PropertyValueWithStr(strconv.FormatUint(invalidPacketTypeCounter.Value()+invalidPacketType2Counter.Value(), 10))}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| } |
| if err := checkInspectRecurse(stats, expected); err != nil { |
| t.Error(err) |
| } |
| } |
| |
| childName := "not a real child" |
| if child := v.GetChild(childName); child != nil { |
| t.Errorf("got GetChild(%s) = %s, want = nil", childName, child) |
| } |
| v.info.Config.Router = []tcpip.Address{ipv4Addr} |
| v.info.Config.DNS = []tcpip.Address{ipv4Addr} |
| if diff := cmp.Diff(inspect.Object{ |
| Name: v.name, |
| Properties: []inspect.Property{ |
| {Key: "State", Value: inspect.PropertyValueWithStr("initSelecting")}, |
| {Key: "AcquiredAddress", Value: inspect.PropertyValueWithStr("[none]")}, |
| {Key: "AssignedAddress", Value: inspect.PropertyValueWithStr("[none]")}, |
| {Key: "Acquisition", Value: inspect.PropertyValueWithStr("0s")}, |
| {Key: "Backoff", Value: inspect.PropertyValueWithStr("0s")}, |
| {Key: "Retransmission", Value: inspect.PropertyValueWithStr("0s")}, |
| {Key: "LeaseExpiration", Value: inspect.PropertyValueWithStr("m=+0.000000000")}, |
| {Key: "RenewTime", Value: inspect.PropertyValueWithStr("m=+0.000000000")}, |
| {Key: "RebindTime", Value: inspect.PropertyValueWithStr("m=+0.000000000")}, |
| {Key: "Config.ServerAddress", Value: inspect.PropertyValueWithStr("[none]")}, |
| {Key: "Config.SubnetMask", Value: inspect.PropertyValueWithStr("[none]")}, |
| {Key: "Config.Router0", Value: inspect.PropertyValueWithStr(ipv4Addr.String())}, |
| {Key: "Config.DNS0", Value: inspect.PropertyValueWithStr(ipv4Addr.String())}, |
| {Key: "Config.LeaseLength", Value: inspect.PropertyValueWithStr("0s")}, |
| {Key: "Config.RenewTime", Value: inspect.PropertyValueWithStr("0s")}, |
| {Key: "Config.RebindTime", Value: inspect.PropertyValueWithStr("0s")}, |
| {Key: "Config.Declined", Value: inspect.PropertyValueWithStr("false")}, |
| }, |
| }, v.ReadData(), cmpopts.IgnoreUnexported(inspect.Object{}, inspect.Property{}, inspect.Metric{})); diff != "" { |
| t.Errorf("ReadData() mismatch (-want +got):\n%s", diff) |
| } |
| |
| { |
| child := v.GetChild("DHCP State Recent History") |
| recentHistory, ok := child.(*circularLogsInspectImpl) |
| if !ok { |
| t.Errorf("got GetChild(DHCP State Recent History) %#v, want %T", child, (*statCounterInspectImpl)(nil)) |
| } |
| |
| if recentHistory != nil { |
| expectedChildren := []string{"0", "1"} |
| expectedChildrenData := []inspect.Object{ |
| { |
| Name: "0", |
| Properties: []inspect.Property{ |
| {Key: "@time", Value: inspect.PropertyValueWithStr("1")}, |
| {Key: "value", Value: inspect.PropertyValueWithStr("1")}, |
| }, |
| }, |
| { |
| Name: "1", |
| Properties: []inspect.Property{ |
| {Key: "@time", Value: inspect.PropertyValueWithStr("2")}, |
| {Key: "value", Value: inspect.PropertyValueWithStr("2")}, |
| }, |
| }, |
| } |
| |
| errors := circularLogsChecker(recentHistory, expectedChildren, expectedChildrenData) |
| for _, err := range errors { |
| t.Error(err) |
| } |
| } |
| } |
| } |
| |
| func TestEthInfoInspectImpl(t *testing.T) { |
| const topopath, filepath = "topopath", "filepath" |
| const features = ethernet.FeaturesWlan | ethernet.FeaturesSynthetic | ethernet.FeaturesLoopback |
| |
| device := ethernetext.Device{ |
| TB: t, |
| GetInfoImpl: func() (ethernet.Info, error) { |
| return ethernet.Info{ |
| Features: features, |
| }, nil |
| }, |
| GetFifosImpl: func() (int32, *ethernet.Fifos, error) { |
| return int32(zx.ErrOk), ðernet.Fifos{ |
| RxDepth: 1, |
| TxDepth: 1, |
| }, nil |
| }, |
| SetIoBufferImpl: func(zx.VMO) (int32, error) { |
| return int32(zx.ErrOk), nil |
| }, |
| StopImpl: func() error { |
| return nil |
| }, |
| SetClientNameImpl: func(string) (int32, error) { |
| return int32(zx.ErrOk), nil |
| }, |
| ConfigMulticastSetPromiscuousModeImpl: func(bool) (int32, error) { |
| return int32(zx.ErrOk), nil |
| }, |
| } |
| |
| client, err := eth.NewClient("client", topopath, filepath, &device) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer func() { |
| _ = client.Close() |
| client.Wait() |
| }() |
| |
| v := ethInfoInspectImpl{ |
| name: "doesn't matter", |
| value: client, |
| } |
| children := v.ListChildren() |
| if diff := cmp.Diff([]string{ |
| "RxReads", |
| "RxWrites", |
| "TxReads", |
| "TxWrites", |
| }, children); diff != "" { |
| t.Errorf("ListChildren() mismatch (-want +got):\n%s", diff) |
| } |
| for _, childName := range children { |
| if child := v.GetChild(childName); child == nil { |
| t.Errorf("got GetChild(%s) = nil, want non-nil", childName) |
| } else if _, ok := child.(*fifoStatsInspectImpl); !ok { |
| t.Errorf("got GetChild(%s) = %#v, want %T", childName, child, (*fifoStatsInspectImpl)(nil)) |
| } |
| } |
| |
| childName := "not a real child" |
| if child := v.GetChild(childName); child != nil { |
| t.Errorf("got GetChild(%s) = %s, want = nil", childName, child) |
| } |
| |
| if diff := cmp.Diff(inspect.Object{ |
| Name: v.name, |
| Metrics: []inspect.Metric{ |
| {Key: "TxDrops", Value: inspect.MetricValueWithUintValue(client.TxStats().Drops.Value())}, |
| }, |
| Properties: []inspect.Property{ |
| {Key: "Topopath", Value: inspect.PropertyValueWithStr(topopath)}, |
| {Key: "Filepath", Value: inspect.PropertyValueWithStr(filepath)}, |
| {Key: "Features", Value: inspect.PropertyValueWithStr(features.String())}, |
| }, |
| }, v.ReadData(), cmpopts.IgnoreUnexported(inspect.Object{}, inspect.Property{}, inspect.Metric{})); diff != "" { |
| t.Errorf("ReadData() mismatch (-want +got):\n%s", diff) |
| } |
| } |
| |
| func TestFifoStatsInfoInspectImpl(t *testing.T) { |
| tests := []struct { |
| name string |
| impl func() fifoStatsInspectImpl |
| wantData func() inspect.Object |
| }{ |
| { |
| name: "size 2", |
| impl: func() fifoStatsInspectImpl { |
| var zeroCounter, nonZeroCounter tcpip.StatCounter |
| nonZeroCounter.IncrementBy(5) |
| return fifoStatsInspectImpl{ |
| name: "size 2", |
| value: func(batch uint32) *tcpip.StatCounter { |
| if batch%2 == 0 { |
| return &zeroCounter |
| } |
| return &nonZeroCounter |
| }, |
| size: 2, |
| } |
| }, |
| wantData: func() inspect.Object { |
| return inspect.Object{ |
| Name: "size 2", |
| Metrics: []inspect.Metric{ |
| {Key: "1", Value: inspect.MetricValueWithUintValue(5)}, |
| }, |
| } |
| }, |
| }, |
| { |
| name: "size 2001", |
| impl: func() fifoStatsInspectImpl { |
| var zeroCounter, nonZeroCounter tcpip.StatCounter |
| nonZeroCounter.IncrementBy(5) |
| return fifoStatsInspectImpl{ |
| name: "size 2001", |
| value: func(batch uint32) *tcpip.StatCounter { |
| if batch == 1 || batch == 2000 || batch == 2001 { |
| return &nonZeroCounter |
| } |
| return &zeroCounter |
| }, |
| size: 2001, |
| } |
| }, |
| wantData: func() inspect.Object { |
| return inspect.Object{ |
| Name: "size 2001", |
| Metrics: []inspect.Metric{ |
| { |
| Key: "1-2", |
| Value: inspect.MetricValueWithUintValue(5), |
| }, |
| { |
| Key: "1999-2000", |
| Value: inspect.MetricValueWithUintValue(5), |
| }, |
| { |
| Key: "2001", |
| Value: inspect.MetricValueWithUintValue(5), |
| }, |
| }, |
| } |
| }, |
| }, |
| { |
| name: "size 2048", |
| impl: func() fifoStatsInspectImpl { |
| var nonZeroCounter tcpip.StatCounter |
| nonZeroCounter.IncrementBy(5) |
| return fifoStatsInspectImpl{ |
| name: "size 2048", |
| value: func(_ uint32) *tcpip.StatCounter { |
| return &nonZeroCounter |
| }, |
| size: 2048, |
| } |
| }, |
| wantData: func() inspect.Object { |
| metrics := []inspect.Metric{} |
| for i := 0; i < 2048; i = i + 2 { |
| metrics = append(metrics, inspect.Metric{ |
| Key: fmt.Sprintf("%d-%d", i+1, i+2), |
| Value: inspect.MetricValueWithUintValue(10), |
| }) |
| |
| } |
| return inspect.Object{ |
| Name: "size 2048", |
| Metrics: metrics, |
| } |
| }, |
| }, |
| } |
| |
| for _, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| v := test.impl() |
| |
| // ListChildren always returns nil. |
| children := v.ListChildren() |
| if diff := cmp.Diff(children, []string(nil)); diff != "" { |
| t.Errorf("ListChildren() mismatch (-want +got):\n%s", diff) |
| } |
| |
| // GetChild always returns nil. |
| childName := "not a real child" |
| if child := v.GetChild(childName); child != nil { |
| t.Errorf("got GetChild(%s) = %s, want = nil", childName, child) |
| } |
| |
| data := v.ReadData() |
| if l := len(data.Metrics); l > maxMetricsForFifoStats { |
| t.Errorf("the length of Metrics (%d) exceeds maxMetricsForFifoStats(%d)", l, maxMetricsForFifoStats) |
| } |
| if diff := cmp.Diff(test.wantData(), data, cmpopts.IgnoreUnexported(inspect.Object{}, inspect.Property{}, inspect.Metric{})); diff != "" { |
| t.Errorf("ReadData() mismatch (-want +got):\n%s", diff) |
| } |
| }) |
| } |
| } |
| |
| func TestInspectGetMissingChild(t *testing.T) { |
| impl := inspectImpl{ |
| inner: &nicInfoMapInspectImpl{}, |
| } |
| req, proxy, err := inspect.NewInspectWithCtxInterfaceRequest() |
| if err != nil { |
| t.Fatalf("inspect.NewInspectWithCtxInterfaceRequest() = %s", err) |
| } |
| defer func() { |
| if err := proxy.Close(); err != nil { |
| t.Fatalf("proxy.Close() = %s", err) |
| } |
| }() |
| found, err := impl.OpenChild(context.Background(), "non-existing-child", req) |
| if err != nil { |
| t.Fatalf("impl.OpenChild(...) = %s", err) |
| } |
| if found { |
| t.Fatalf("got impl.OpenChild(...) = true, want = false") |
| } |
| // The request channel must have been closed. |
| if status := zx.Sys_object_wait_one(*proxy.Channel.Handle(), zx.SignalChannelPeerClosed, 0, nil); status != zx.ErrOk { |
| t.Fatalf("zx.Sys_object_wait_one(_, zx.SignalChannelPeerClosed, 0, nil) = %s", status) |
| } |
| } |
| |
| func TestRoutingTableInspectImpl(t *testing.T) { |
| impl := routingTableInspectImpl{ |
| value: []routes.ExtendedRoute{ |
| {}, {}, |
| }, |
| } |
| children := impl.ListChildren() |
| if diff := cmp.Diff([]string{ |
| "0", "1", |
| }, children); diff != "" { |
| t.Errorf("ListChildren() mismatch (-want +got):\n%s", diff) |
| } |
| for _, childName := range children { |
| if child := impl.GetChild(childName); child == nil { |
| t.Errorf("got GetChild(%s) = nil, want non-nil", childName) |
| } else if _, ok := child.(*routeInfoInspectImpl); !ok { |
| t.Errorf("got GetChild(%s) = %#v, want %T", childName, child, (*routeInfoInspectImpl)(nil)) |
| } |
| } |
| |
| childName := "not a real child" |
| if got := impl.GetChild(childName); got != nil { |
| t.Errorf("got GetChild(%s) = %s, want = nil", childName, got) |
| } |
| |
| // Index past the end of the routing table |
| childName = "2" |
| if got := impl.GetChild(childName); got != nil { |
| t.Errorf("got GetChild(%s) = %s, want = nil", childName, got) |
| } |
| if diff := cmp.Diff(inspect.Object{ |
| Name: "Routes", |
| }, impl.ReadData(), cmpopts.IgnoreUnexported(inspect.Object{})); diff != "" { |
| t.Errorf("ReadData() mismatch (-want +got):\n%s", diff) |
| } |
| } |
| |
| func TestRouteInfoInspectImpl(t *testing.T) { |
| tests := []struct { |
| name string |
| route routes.ExtendedRoute |
| properties []inspect.Property |
| }{ |
| { |
| name: "IPv4", |
| route: routes.ExtendedRoute{ |
| Route: tcpip.Route{ |
| Destination: header.IPv4EmptySubnet, |
| Gateway: "\x01\x02\x03\x04", |
| NIC: 1, |
| }, |
| Metric: 42, |
| MetricTracksInterface: true, |
| Dynamic: true, |
| Enabled: true, |
| }, |
| properties: []inspect.Property{ |
| {Key: "Destination", Value: inspect.PropertyValueWithStr("0.0.0.0/0")}, |
| {Key: "Gateway", Value: inspect.PropertyValueWithStr("1.2.3.4")}, |
| {Key: "NIC", Value: inspect.PropertyValueWithStr("1")}, |
| {Key: "Metric", Value: inspect.PropertyValueWithStr("42")}, |
| {Key: "MetricTracksInterface", Value: inspect.PropertyValueWithStr("true")}, |
| {Key: "Dynamic", Value: inspect.PropertyValueWithStr("true")}, |
| {Key: "Enabled", Value: inspect.PropertyValueWithStr("true")}, |
| }, |
| }, |
| { |
| name: "IPv6", |
| route: routes.ExtendedRoute{ |
| Route: tcpip.Route{ |
| Destination: header.IPv6EmptySubnet, |
| Gateway: "\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", |
| NIC: 2, |
| }, |
| Metric: 0, |
| MetricTracksInterface: false, |
| Dynamic: true, |
| Enabled: true, |
| }, |
| properties: []inspect.Property{ |
| {Key: "Destination", Value: inspect.PropertyValueWithStr("::/0")}, |
| {Key: "Gateway", Value: inspect.PropertyValueWithStr("fe80::1")}, |
| {Key: "NIC", Value: inspect.PropertyValueWithStr("2")}, |
| {Key: "Metric", Value: inspect.PropertyValueWithStr("0")}, |
| {Key: "MetricTracksInterface", Value: inspect.PropertyValueWithStr("false")}, |
| {Key: "Dynamic", Value: inspect.PropertyValueWithStr("true")}, |
| {Key: "Enabled", Value: inspect.PropertyValueWithStr("true")}, |
| }, |
| }, |
| } |
| |
| for _, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| impl := routeInfoInspectImpl{name: "0", value: test.route} |
| |
| children := impl.ListChildren() |
| if diff := cmp.Diff(children, []string(nil)); diff != "" { |
| t.Errorf("ListChildren() mismatch (-want +got):\n%s", diff) |
| } |
| |
| childName := "not a real child" |
| if got := impl.GetChild(childName); got != nil { |
| t.Errorf("got GetChild(%s) = %s, want = nil", childName, got) |
| } |
| |
| if diff := cmp.Diff( |
| inspect.Object{Name: "0", Properties: test.properties}, |
| impl.ReadData(), |
| cmpopts.IgnoreUnexported(inspect.Object{}, inspect.Property{}, inspect.Metric{}), |
| ); diff != "" { |
| t.Errorf("ReadData() mismatch (-want +got):\n%s", diff) |
| } |
| }) |
| } |
| } |
| |
| func TestNetworkEndpointStatsInspectImpl(t *testing.T) { |
| const unknownNetworkProtocolNumber = 0 |
| impl := networkEndpointStatsInspectImpl{ |
| name: "Network Endpoint Stats", |
| value: map[string]stack.NetworkEndpointStats{ |
| "IPv4": &ipv4.Stats{}, |
| "IPv6": &ipv6.Stats{}, |
| "ARP": &arp.Stats{}, |
| "Random Name": &arp.Stats{}, |
| }, |
| } |
| |
| expectedProtocols := []string{"IPv4", "IPv6", "ARP", "Random Name"} |
| |
| children := impl.ListChildren() |
| if diff := cmp.Diff(expectedProtocols, children, cmpopts.SortSlices(func(a, b string) bool { |
| return a < b |
| })); diff != "" { |
| t.Errorf("ListChildren() mismatch (-want +got):\n%s", diff) |
| } |
| |
| for _, childName := range children { |
| if child := impl.GetChild(childName); child == nil { |
| t.Errorf("got GetChild(%s) = nil, want non-nil", childName) |
| } else if _, ok := child.(*statCounterInspectImpl); !ok { |
| t.Errorf("got GetChild(%s) = %#v, want %T", childName, child, (*statCounterInspectImpl)(nil)) |
| } |
| } |
| |
| childName := "not a real child" |
| if got := impl.GetChild(childName); got != nil { |
| t.Errorf("got GetChild(%s) = %s, want = nil", childName, got) |
| } |
| |
| if diff := cmp.Diff(inspect.Object{ |
| Name: "Network Endpoint Stats", |
| }, impl.ReadData(), cmpopts.IgnoreUnexported(inspect.Object{})); diff != "" { |
| t.Errorf("ReadData() mismatch (-want +got):\n%s", diff) |
| } |
| } |
| |
| func TestNeighborTableInspectImpl(t *testing.T) { |
| impl := neighborTableInspectImpl{ |
| name: neighborsLabel, |
| value: map[string]stack.NeighborEntry{ |
| ipv4Addr.String(): { |
| Addr: ipv4Addr, |
| LinkAddr: "\x0a\x00\x00\x00\x00\x01", |
| State: stack.Reachable, |
| UpdatedAt: time.Unix(0, 1), |
| }, |
| ipv6Addr.String(): { |
| Addr: ipv6Addr, |
| LinkAddr: "\x0a\x00\x00\x00\x00\x02", |
| State: stack.Stale, |
| UpdatedAt: time.Unix(0, 2), |
| }, |
| }, |
| } |
| |
| children := impl.ListChildren() |
| if diff := cmp.Diff([]string{ |
| ipv4Addr.String(), ipv6Addr.String(), |
| }, children, cmpopts.SortSlices(func(a, b string) bool { |
| return a < b |
| })); diff != "" { |
| t.Errorf("ListChildren() mismatch (-want +got):\n%s", diff) |
| } |
| for _, childName := range children { |
| if child := impl.GetChild(childName); child == nil { |
| t.Errorf("got GetChild(%s) = nil, want non-nil", childName) |
| } else if _, ok := child.(*neighborInfoInspectImpl); !ok { |
| t.Errorf("got GetChild(%s) = %#v, want %T", childName, child, (*neighborInfoInspectImpl)(nil)) |
| } |
| } |
| |
| childName := "not a real child" |
| if got := impl.GetChild(childName); got != nil { |
| t.Errorf("got GetChild(%s) = %s, want = nil", childName, got) |
| } |
| |
| if diff := cmp.Diff(inspect.Object{ |
| Name: neighborsLabel, |
| }, impl.ReadData(), cmpopts.IgnoreUnexported(inspect.Object{})); diff != "" { |
| t.Errorf("ReadData() mismatch (-want +got):\n%s", diff) |
| } |
| } |
| |
| func TestNeighborInfoInspectImpl(t *testing.T) { |
| tests := []struct { |
| name string |
| neighbor stack.NeighborEntry |
| }{ |
| { |
| name: "IPv4", |
| neighbor: stack.NeighborEntry{ |
| Addr: ipv4Addr, |
| LinkAddr: "\x0a\x00\x00\x00\x00\x01", |
| State: stack.Reachable, |
| UpdatedAt: time.Unix(0, 1), |
| }, |
| }, |
| { |
| name: "IPv6", |
| neighbor: stack.NeighborEntry{ |
| Addr: ipv6Addr, |
| LinkAddr: "\x0a\x00\x00\x00\x00\x02", |
| State: stack.Stale, |
| UpdatedAt: time.Unix(0, 2), |
| }, |
| }, |
| } |
| |
| for _, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| impl := neighborInfoInspectImpl{value: test.neighbor} |
| if diff := cmp.Diff(impl.ListChildren(), []string(nil)); diff != "" { |
| t.Errorf("ListChildren() mismatch (-want +got):\n%s", diff) |
| } |
| |
| childName := "not a real child" |
| if got := impl.GetChild(childName); got != nil { |
| t.Errorf("got GetChild(%s) = %s, want = nil", childName, got) |
| } |
| |
| if diff := cmp.Diff( |
| inspect.Object{ |
| Name: test.neighbor.Addr.String(), |
| Properties: []inspect.Property{ |
| {Key: "Link address", Value: inspect.PropertyValueWithStr(test.neighbor.LinkAddr.String())}, |
| {Key: "State", Value: inspect.PropertyValueWithStr(test.neighbor.State.String())}, |
| }, |
| Metrics: []inspect.Metric{ |
| {Key: "Last updated", Value: inspect.MetricValueWithIntValue(test.neighbor.UpdatedAt.UnixNano())}, |
| }, |
| }, |
| impl.ReadData(), |
| cmpopts.IgnoreUnexported(inspect.Object{}, inspect.Property{}, inspect.Metric{}), |
| ); diff != "" { |
| t.Errorf("ReadData() mismatch (-want +got):\n%s", diff) |
| } |
| }) |
| } |
| } |