1// Copyright 2019 The Prometheus Authors
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14package procfs
15
16import (
17	"os"
18	"strings"
19	"testing"
20
21	"github.com/google/go-cmp/cmp"
22)
23
24// The majority of test cases are covered in Test_parseSockstat. The top level
25// tests just check the basics for integration test purposes.
26
27func TestNetSockstat(t *testing.T) {
28	fs, err := NewFS(procTestFixtures)
29	if err != nil {
30		t.Fatalf("failed to open procfs: %v", err)
31	}
32
33	stat, err := fs.NetSockstat()
34	if err != nil {
35		t.Fatalf("failed to get sockstat: %v", err)
36	}
37
38	// IPv4 stats should include Used.
39	if stat.Used == nil {
40		t.Fatalf("IPv4 sockstat used value was nil")
41	}
42	if diff := cmp.Diff(1602, *stat.Used); diff != "" {
43		t.Fatalf("unexpected IPv4 used sockets (-want +got):\n%s", diff)
44	}
45
46	// TCP occurs first; do a basic sanity check.
47	if diff := cmp.Diff("TCP", stat.Protocols[0].Protocol); diff != "" {
48		t.Fatalf("unexpected socket protocol (-want +got):\n%s", diff)
49	}
50	if diff := cmp.Diff(35, stat.Protocols[0].InUse); diff != "" {
51		t.Fatalf("unexpected number of TCP sockets (-want +got):\n%s", diff)
52	}
53}
54
55func TestNetSockstat6(t *testing.T) {
56	fs, err := NewFS(procTestFixtures)
57	if err != nil {
58		t.Fatalf("failed to open procfs: %v", err)
59	}
60
61	stat, err := fs.NetSockstat6()
62	if err != nil {
63		t.Fatalf("failed to get sockstat: %v", err)
64	}
65
66	// IPv6 stats should not include Used.
67	if stat.Used != nil {
68		t.Fatalf("IPv6 sockstat used value was not nil")
69	}
70
71	// TCP6 occurs first; do a basic sanity check.
72	if diff := cmp.Diff("TCP6", stat.Protocols[0].Protocol); diff != "" {
73		t.Fatalf("unexpected socket protocol (-want +got):\n%s", diff)
74	}
75	if diff := cmp.Diff(17, stat.Protocols[0].InUse); diff != "" {
76		t.Fatalf("unexpected number of TCP sockets (-want +got):\n%s", diff)
77	}
78}
79
80func Test_readSockstatIsNotExist(t *testing.T) {
81	// On a machine with IPv6 disabled for example, we want to ensure that
82	// readSockstat returns an error that is compatible with os.IsNotExist.
83	//
84	// We can use a synthetic file path here to verify this behavior.
85	_, err := readSockstat("/does/not/exist")
86	if err == nil || !os.IsNotExist(err) {
87		t.Fatalf("error is not compatible with os.IsNotExist: %#v", err)
88	}
89}
90
91func Test_parseSockstat(t *testing.T) {
92	tests := []struct {
93		name string
94		s    string
95		ok   bool
96		stat *NetSockstat
97	}{
98		{
99			name: "empty",
100			ok:   true,
101			stat: &NetSockstat{},
102		},
103		{
104			name: "bad line",
105			s: `
106sockets: used
107`,
108		},
109		{
110			name: "bad key/value pairs",
111			s: `
112TCP: inuse 32 orphan
113`,
114		},
115		{
116			name: "IPv4",
117			s: `
118sockets: used 1591
119TCP: inuse 32 orphan 0 tw 0 alloc 58 mem 13
120UDP: inuse 8 mem 115
121UDPLITE: inuse 0
122RAW: inuse 0
123FRAG: inuse 0 memory 0
124			`,
125			ok: true,
126			stat: &NetSockstat{
127				Used: intp(1591),
128				Protocols: []NetSockstatProtocol{
129					{
130						Protocol: "TCP",
131						InUse:    32,
132						Orphan:   intp(0),
133						TW:       intp(0),
134						Alloc:    intp(58),
135						Mem:      intp(13),
136					},
137					{
138						Protocol: "UDP",
139						InUse:    8,
140						Mem:      intp(115),
141					},
142					{
143						Protocol: "UDPLITE",
144					},
145					{
146						Protocol: "RAW",
147					},
148					{
149						Protocol: "FRAG",
150						Memory:   intp(0),
151					},
152				},
153			},
154		},
155		{
156			name: "IPv6",
157			s: `
158TCP6: inuse 24
159UDP6: inuse 9
160UDPLITE6: inuse 0
161RAW6: inuse 1
162FRAG6: inuse 0 memory 0
163			`,
164			ok: true,
165			stat: &NetSockstat{
166				Protocols: []NetSockstatProtocol{
167					{
168						Protocol: "TCP6",
169						InUse:    24,
170					},
171					{
172						Protocol: "UDP6",
173						InUse:    9,
174					},
175					{
176						Protocol: "UDPLITE6",
177					},
178					{
179						Protocol: "RAW6",
180						InUse:    1,
181					},
182					{
183						Protocol: "FRAG6",
184						Memory:   intp(0),
185					},
186				},
187			},
188		},
189	}
190
191	for _, tt := range tests {
192		t.Run(tt.name, func(t *testing.T) {
193			stat, err := parseSockstat(strings.NewReader(strings.TrimSpace(tt.s)))
194			if err != nil {
195				if tt.ok {
196					t.Fatalf("failed to parse sockstats: %v", err)
197				}
198
199				t.Logf("OK error: %v", err)
200				return
201			}
202			if !tt.ok {
203				t.Fatal("expected an error, but none occurred")
204			}
205
206			if diff := cmp.Diff(tt.stat, stat); diff != "" {
207				t.Errorf("unexpected sockstats (-want +got):\n%s", diff)
208			}
209		})
210	}
211}
212
213func intp(i int) *int { return &i }
214