File: log_test.go

package info (click to toggle)
golang-github-hashicorp-raft 1.5.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 920 kB
  • sloc: makefile: 41; sh: 9
file content (158 lines) | stat: -rw-r--r-- 3,221 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package raft

import (
	"bytes"
	"fmt"
	"testing"
	"time"

	metrics "github.com/armon/go-metrics"
)

func TestOldestLog(t *testing.T) {
	cases := []struct {
		Name    string
		Logs    []*Log
		WantIdx uint64
		WantErr bool
	}{
		{
			Name:    "empty logs",
			Logs:    nil,
			WantIdx: 0,
			WantErr: true,
		},
		{
			Name: "simple case",
			Logs: []*Log{
				&Log{
					Index: 1234,
					Term:  1,
				},
				&Log{
					Index: 1235,
					Term:  1,
				},
				&Log{
					Index: 1236,
					Term:  2,
				},
			},
			WantIdx: 1234,
			WantErr: false,
		},
	}

	for _, tc := range cases {
		tc := tc
		t.Run(tc.Name, func(t *testing.T) {
			s := NewInmemStore()
			if err := s.StoreLogs(tc.Logs); err != nil {
				t.Fatalf("expected store logs not to fail: %s", err)
			}

			got, err := oldestLog(s)
			switch {
			case tc.WantErr && err == nil:
				t.Fatalf("wanted error got nil")
			case !tc.WantErr && err != nil:
				t.Fatalf("wanted no error got: %s", err)
			}

			if got.Index != tc.WantIdx {
				t.Fatalf("got index %v, want %v", got.Index, tc.WantIdx)
			}
		})
	}
}

func TestEmitsLogStoreMetrics(t *testing.T) {
	sink := testSetupMetrics(t)

	start := time.Now()

	s := NewInmemStore()
	logs := []*Log{
		&Log{
			Index:      1234,
			Term:       1,
			AppendedAt: time.Now(),
		},
		&Log{
			Index: 1235,
			Term:  1,
		},
		&Log{
			Index: 1236,
			Term:  2,
		},
	}
	if err := s.StoreLogs(logs); err != nil {
		t.Fatalf("expected store logs not to fail: %s", err)
	}

	stopCh := make(chan struct{})
	defer close(stopCh)

	go emitLogStoreMetrics(s, []string{"foo"}, time.Millisecond, stopCh)

	// Wait for at least one interval
	time.Sleep(5 * time.Millisecond)

	got := getCurrentGaugeValue(t, sink, "raft.test.foo.oldestLogAge")

	// Assert the age is in a reasonable range.
	if got > float32(time.Since(start).Milliseconds()) {
		t.Fatalf("max age before test start: %v", got)
	}

	if got < 1 {
		t.Fatalf("max age less than interval: %v", got)
	}
}

func testSetupMetrics(t *testing.T) *metrics.InmemSink {
	// Record for ages (5 mins) so we can be confident that our assertions won't
	// fail on silly long test runs due to dropped data.
	s := metrics.NewInmemSink(10*time.Second, 300*time.Second)
	cfg := metrics.DefaultConfig("raft.test")
	cfg.EnableHostname = false
	metrics.NewGlobal(cfg, s)
	return s
}

func getCurrentGaugeValue(t *testing.T, sink *metrics.InmemSink, name string) float32 {
	t.Helper()

	data := sink.Data()

	// Loop backward through intervals until there is a non-empty one
	// Addresses flakiness around recording to one interval but accessing during the next
	for i := len(data) - 1; i >= 0; i-- {
		currentInterval := data[i]

		currentInterval.RLock()
		if gv, ok := currentInterval.Gauges[name]; ok {
			currentInterval.RUnlock()
			return gv.Value
		}
		currentInterval.RUnlock()
	}

	// Debug print all the gauges
	buf := bytes.NewBuffer(nil)
	for _, intv := range data {
		intv.RLock()
		for name, val := range intv.Gauges {
			fmt.Fprintf(buf, "[%v][G] '%s': %0.3f\n", intv.Interval, name, val.Value)
		}
		intv.RUnlock()
	}
	t.Log(buf.String())

	t.Fatalf("didn't find gauge %q", name)
	return 0
}