1package memsize
2
3import (
4	"testing"
5	"unsafe"
6)
7
8const (
9	sizeofSlice     = unsafe.Sizeof([]byte{})
10	sizeofMap       = unsafe.Sizeof(map[string]string{})
11	sizeofInterface = unsafe.Sizeof((interface{})(nil))
12	sizeofString    = unsafe.Sizeof("")
13	sizeofWord      = unsafe.Sizeof(uintptr(0))
14	sizeofChan      = unsafe.Sizeof(make(chan struct{}))
15)
16
17type (
18	struct16 struct {
19		x, y uint64
20	}
21	structptr struct {
22		x   uint32
23		cld *structptr
24	}
25	structuint32ptr struct {
26		x *uint32
27	}
28	structmultiptr struct {
29		s1 *structptr
30		u1 *structuint32ptr
31		s2 *structptr
32		u2 *structuint32ptr
33		s3 *structptr
34		u3 *structuint32ptr
35	}
36	structarrayptr struct {
37		x *uint64
38		a [10]uint64
39	}
40	structiface struct {
41		s *struct16
42		x interface{}
43	}
44	struct64array  struct{ array64 }
45	structslice    struct{ s []uint32 }
46	structstring   struct{ s string }
47	structloop     struct{ s *structloop }
48	structptrslice struct{ s *structslice }
49	array64        [64]byte
50)
51
52func TestTotal(t *testing.T) {
53	tests := []struct {
54		name string
55		v    interface{}
56		want uintptr
57	}{
58		{
59			name: "struct16",
60			v:    &struct16{},
61			want: 16,
62		},
63		{
64			name: "structptr_nil",
65			v:    &structptr{},
66			want: 2 * sizeofWord,
67		},
68		{
69			name: "structptr",
70			v:    &structptr{cld: &structptr{}},
71			want: 2 * 2 * sizeofWord,
72		},
73		{
74			name: "structptr_loop",
75			v: func() *structptr {
76				v := &structptr{}
77				v.cld = v
78				return v
79			}(),
80			want: 2 * sizeofWord,
81		},
82		{
83			name: "structmultiptr_loop",
84			v: func() *structmultiptr {
85				v1 := &structptr{x: 1}
86				v2 := &structptr{x: 2, cld: v1}
87				return &structmultiptr{s1: v1, s2: v1, s3: v2}
88			}(),
89			want: 6*sizeofWord /* structmultiptr */ + 2*2*sizeofWord, /* structptr */
90		},
91		{
92			name: "structmultiptr_interior",
93			v: func() *structmultiptr {
94				v1 := &structptr{x: 1}
95				v2 := &structptr{x: 2}
96				return &structmultiptr{
97					// s1 is scanned before u1, which has a reference to a field of s1.
98					s1: v1,
99					u1: &structuint32ptr{x: &v1.x},
100					// This one goes the other way around: u2, which has a reference to a
101					// field of s3 is scanned before s3.
102					u2: &structuint32ptr{x: &v2.x},
103					s3: v2,
104				}
105			}(),
106			want: 6*sizeofWord /* structmultiptr */ + 2*2*sizeofWord /* structptr */ + 2*sizeofWord, /* structuint32ptr */
107		},
108		{
109			name: "struct64array",
110			v:    &struct64array{},
111			want: 64,
112		},
113		{
114			name: "structptrslice",
115			v:    &structptrslice{&structslice{s: []uint32{1, 2, 3}}},
116			want: sizeofWord + sizeofSlice + 3*4,
117		},
118		{
119			name: "array_unadressable",
120			v: func() *map[[3]uint64]struct{} {
121				v := map[[3]uint64]struct{}{
122					{1, 2, 3}: struct{}{},
123				}
124				return &v
125			}(),
126			want: sizeofMap + 3*8,
127		},
128		{
129			name: "structslice",
130			v:    &structslice{s: []uint32{1, 2, 3}},
131			want: sizeofSlice + 3*4,
132		},
133		{
134			name: "structloop",
135			v: func() *structloop {
136				v := new(structloop)
137				v.s = v
138				return v
139			}(),
140			want: sizeofWord,
141		},
142		{
143			name: "array64",
144			v:    &array64{},
145			want: 64,
146		},
147		{
148			name: "byteslice",
149			v:    &[]byte{1, 2, 3},
150			want: sizeofSlice + 3,
151		},
152		{
153			name: "slice3_ptrval",
154			v:    &[]*struct16{{}, {}, {}},
155			want: sizeofSlice + 3*sizeofWord + 3*16,
156		},
157		{
158			name: "map3",
159			v:    &map[uint64]uint64{1: 1, 2: 2, 3: 3},
160			want: sizeofMap + 3*8 /* keys */ + 3*8, /* values */
161		},
162		{
163			name: "map3_ptrval",
164			v:    &map[uint64]*struct16{1: {}, 2: {}, 3: {}},
165			want: sizeofMap + 3*8 /* keys */ + 3*sizeofWord /* value pointers */ + 3*16, /* values */
166		},
167		{
168			name: "map3_ptrkey",
169			v:    &map[*struct16]uint64{{x: 1}: 1, {x: 2}: 2, {x: 3}: 3},
170			want: sizeofMap + 3*sizeofWord /* key pointers */ + 3*16 /* keys */ + 3*8, /* values */
171		},
172		{
173			name: "map_interface",
174			v:    &map[interface{}]interface{}{"aa": uint64(1)},
175			want: sizeofMap + sizeofInterface + sizeofString + 2 /* key */ + sizeofInterface + 8, /* value */
176		},
177		{
178			name: "pointerpointer",
179			v: func() **uint64 {
180				i := uint64(0)
181				p := &i
182				return &p
183			}(),
184			want: sizeofWord + 8,
185		},
186		{
187			name: "structstring",
188			v:    &structstring{"123"},
189			want: sizeofString + 3,
190		},
191		{
192			name: "slices_samearray",
193			v: func() *[3][]byte {
194				backarray := [64]byte{}
195				return &[3][]byte{
196					backarray[16:],
197					backarray[4:16],
198					backarray[0:4],
199				}
200			}(),
201			want: 3*sizeofSlice + 64,
202		},
203		{
204			name: "slices_nil",
205			v: func() *[2][]byte {
206				return &[2][]byte{nil, nil}
207			}(),
208			want: 2 * sizeofSlice,
209		},
210		{
211			name: "slices_overlap_total",
212			v: func() *[2][]byte {
213				backarray := [32]byte{}
214				return &[2][]byte{backarray[:], backarray[:]}
215			}(),
216			want: 2*sizeofSlice + 32,
217		},
218		{
219			name: "slices_overlap",
220			v: func() *[4][]uint16 {
221				backarray := [32]uint16{}
222				return &[4][]uint16{
223					backarray[2:4],
224					backarray[10:12],
225					backarray[20:25],
226					backarray[:],
227				}
228			}(),
229			want: 4*sizeofSlice + 32*2,
230		},
231		{
232			name: "slices_overlap_array",
233			v: func() *struct {
234				a [32]byte
235				s [2][]byte
236			} {
237				v := struct {
238					a [32]byte
239					s [2][]byte
240				}{}
241				v.s[0] = v.a[2:4]
242				v.s[1] = v.a[5:8]
243				return &v
244			}(),
245			want: 32 + 2*sizeofSlice,
246		},
247		{
248			name: "interface",
249			v:    &[2]interface{}{uint64(0), &struct16{}},
250			want: 2*sizeofInterface + 8 + 16,
251		},
252		{
253			name: "interface_nil",
254			v:    &[2]interface{}{nil, nil},
255			want: 2 * sizeofInterface,
256		},
257		{
258			name: "structiface_slice",
259			v:    &structiface{x: make([]byte, 10)},
260			want: sizeofWord + sizeofInterface + sizeofSlice + 10,
261		},
262		{
263			name: "structiface_pointer",
264			v: func() *structiface {
265				s := &struct16{1, 2}
266				return &structiface{s: s, x: &s.x}
267			}(),
268			want: sizeofWord + 16 + sizeofInterface,
269		},
270		{
271			name: "empty_chan",
272			v: func() *chan uint64 {
273				c := make(chan uint64)
274				return &c
275			}(),
276			want: sizeofChan,
277		},
278		{
279			name: "empty_closed_chan",
280			v: func() *chan uint64 {
281				c := make(chan uint64)
282				close(c)
283				return &c
284			}(),
285			want: sizeofChan,
286		},
287		{
288			name: "empty_chan_buffer",
289			v: func() *chan uint64 {
290				c := make(chan uint64, 10)
291				return &c
292			}(),
293			want: sizeofChan + 10*8,
294		},
295		{
296			name: "chan_buffer",
297			v: func() *chan uint64 {
298				c := make(chan uint64, 10)
299				for i := 0; i < 8; i++ {
300					c <- 0
301				}
302				return &c
303			}(),
304			want: sizeofChan + 10*8,
305		},
306		{
307			name: "closed_chan_buffer",
308			v: func() *chan uint64 {
309				c := make(chan uint64, 10)
310				for i := 0; i < 8; i++ {
311					c <- 0
312				}
313				close(c)
314				return &c
315			}(),
316			want: sizeofChan + 10*8,
317		},
318		{
319			name: "chan_buffer_escan",
320			v: func() *chan *struct16 {
321				c := make(chan *struct16, 10)
322				for i := 0; i < 8; i++ {
323					c <- &struct16{x: uint64(i)}
324				}
325				return &c
326			}(),
327			want: sizeofChan + 10*sizeofWord + 8*16,
328		},
329		{
330			name: "closed_chan_buffer_escan",
331			v: func() *chan *struct16 {
332				c := make(chan *struct16, 10)
333				for i := 0; i < 8; i++ {
334					c <- &struct16{x: uint64(i)}
335				}
336				close(c)
337				return &c
338			}(),
339			want: sizeofChan + 10*sizeofWord + 8*16,
340		},
341		{
342			name: "nil_chan",
343			v: func() *chan *struct16 {
344				var c chan *struct16
345				return &c
346			}(),
347			want: sizeofChan,
348		},
349	}
350	for _, test := range tests {
351		t.Run(test.name, func(t *testing.T) {
352			size := Scan(test.v)
353			if size.Total != test.want {
354				t.Errorf("total=%d, want %d", size.Total, test.want)
355				t.Logf("\n%s", size.Report())
356			}
357		})
358	}
359}
360