1package memberlist
2
3import (
4	"testing"
5	"time"
6)
7
8func TestSuspicion_remainingSuspicionTime(t *testing.T) {
9	cases := []struct {
10		n        int32
11		k        int32
12		elapsed  time.Duration
13		min      time.Duration
14		max      time.Duration
15		expected time.Duration
16	}{
17		{0, 3, 0, 2 * time.Second, 30 * time.Second, 30 * time.Second},
18		{1, 3, 2 * time.Second, 2 * time.Second, 30 * time.Second, 14 * time.Second},
19		{2, 3, 3 * time.Second, 2 * time.Second, 30 * time.Second, 4810 * time.Millisecond},
20		{3, 3, 4 * time.Second, 2 * time.Second, 30 * time.Second, -2 * time.Second},
21		{4, 3, 5 * time.Second, 2 * time.Second, 30 * time.Second, -3 * time.Second},
22		{5, 3, 10 * time.Second, 2 * time.Second, 30 * time.Second, -8 * time.Second},
23	}
24	for i, c := range cases {
25		remaining := remainingSuspicionTime(c.n, c.k, c.elapsed, c.min, c.max)
26		if remaining != c.expected {
27			t.Errorf("case %d: remaining %9.6f != expected %9.6f", i, remaining.Seconds(), c.expected.Seconds())
28		}
29	}
30}
31
32func TestSuspicion_Timer(t *testing.T) {
33	const k = 3
34	const min = 500 * time.Millisecond
35	const max = 2 * time.Second
36
37	type pair struct {
38		from    string
39		newInfo bool
40	}
41	cases := []struct {
42		numConfirmations int
43		from             string
44		confirmations    []pair
45		expected         time.Duration
46	}{
47		{
48			0,
49			"me",
50			[]pair{},
51			max,
52		},
53		{
54			1,
55			"me",
56			[]pair{
57				pair{"me", false},
58				pair{"foo", true},
59			},
60			1250 * time.Millisecond,
61		},
62		{
63			1,
64			"me",
65			[]pair{
66				pair{"me", false},
67				pair{"foo", true},
68				pair{"foo", false},
69				pair{"foo", false},
70			},
71			1250 * time.Millisecond,
72		},
73		{
74			2,
75			"me",
76			[]pair{
77				pair{"me", false},
78				pair{"foo", true},
79				pair{"bar", true},
80			},
81			810 * time.Millisecond,
82		},
83		{
84			3,
85			"me",
86			[]pair{
87				pair{"me", false},
88				pair{"foo", true},
89				pair{"bar", true},
90				pair{"baz", true},
91			},
92			min,
93		},
94		{
95			3,
96			"me",
97			[]pair{
98				pair{"me", false},
99				pair{"foo", true},
100				pair{"bar", true},
101				pair{"baz", true},
102				pair{"zoo", false},
103			},
104			min,
105		},
106	}
107	for i, c := range cases {
108		ch := make(chan time.Duration, 1)
109		start := time.Now()
110		f := func(numConfirmations int) {
111			if numConfirmations != c.numConfirmations {
112				t.Errorf("case %d: bad %d != %d", i, numConfirmations, c.numConfirmations)
113			}
114
115			ch <- time.Now().Sub(start)
116		}
117
118		// Create the timer and add the requested confirmations. Wait
119		// the fudge amount to help make sure we calculate the timeout
120		// overall, and don't accumulate extra time.
121		s := newSuspicion(c.from, k, min, max, f)
122		fudge := 25 * time.Millisecond
123		for _, p := range c.confirmations {
124			time.Sleep(fudge)
125			if s.Confirm(p.from) != p.newInfo {
126				t.Fatalf("case %d: newInfo mismatch for %s", i, p.from)
127			}
128		}
129
130		// Wait until right before the timeout and make sure the
131		// timer hasn't fired.
132		already := time.Duration(len(c.confirmations)) * fudge
133		time.Sleep(c.expected - already - fudge)
134		select {
135		case d := <-ch:
136			t.Fatalf("case %d: should not have fired (%9.6f)", i, d.Seconds())
137		default:
138		}
139
140		// Wait through the timeout and a little after and make sure it
141		// fires.
142		time.Sleep(2 * fudge)
143		select {
144		case <-ch:
145		default:
146			t.Fatalf("case %d: should have fired", i)
147		}
148
149		// Confirm after to make sure it handles a negative remaining
150		// time correctly and doesn't fire again.
151		s.Confirm("late")
152		time.Sleep(c.expected + 2*fudge)
153		select {
154		case d := <-ch:
155			t.Fatalf("case %d: should not have fired (%9.6f)", i, d.Seconds())
156		default:
157		}
158	}
159}
160
161func TestSuspicion_Timer_ZeroK(t *testing.T) {
162	ch := make(chan struct{}, 1)
163	f := func(int) {
164		ch <- struct{}{}
165	}
166
167	// This should select the min time since there are no expected
168	// confirmations to accelerate the timer.
169	s := newSuspicion("me", 0, 25*time.Millisecond, 30*time.Second, f)
170	if s.Confirm("foo") {
171		t.Fatalf("should not provide new information")
172	}
173
174	select {
175	case <-ch:
176	case <-time.After(50 * time.Millisecond):
177		t.Fatalf("should have fired")
178	}
179}
180
181func TestSuspicion_Timer_Immediate(t *testing.T) {
182	ch := make(chan struct{}, 1)
183	f := func(int) {
184		ch <- struct{}{}
185	}
186
187	// This should underflow the timeout and fire immediately.
188	s := newSuspicion("me", 1, 100*time.Millisecond, 30*time.Second, f)
189	time.Sleep(200 * time.Millisecond)
190	s.Confirm("foo")
191
192	// Wait a little while since the function gets called in a goroutine.
193	select {
194	case <-ch:
195	case <-time.After(25 * time.Millisecond):
196		t.Fatalf("should have fired")
197	}
198}
199