github.com/netdata/go.d.plugin@v0.58.1/modules/chrony/chrony_test.go (about) 1 // SPDX-License-Identifier: GPL-3.0-or-later 2 3 package chrony 4 5 import ( 6 "errors" 7 "net" 8 "testing" 9 "time" 10 11 "github.com/facebook/time/ntp/chrony" 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 ) 15 16 func TestChrony_Init(t *testing.T) { 17 tests := map[string]struct { 18 config Config 19 wantFail bool 20 }{ 21 "default config": { 22 config: New().Config, 23 }, 24 "unset 'address'": { 25 wantFail: true, 26 config: Config{ 27 Address: "", 28 }, 29 }, 30 } 31 32 for name, test := range tests { 33 t.Run(name, func(t *testing.T) { 34 c := New() 35 c.Config = test.config 36 37 if test.wantFail { 38 assert.False(t, c.Init()) 39 } else { 40 assert.True(t, c.Init()) 41 } 42 }) 43 } 44 } 45 46 func TestChrony_Check(t *testing.T) { 47 tests := map[string]struct { 48 prepare func() *Chrony 49 wantFail bool 50 }{ 51 "tracking: success, activity: success": { 52 wantFail: false, 53 prepare: func() *Chrony { return prepareChronyWithMock(&mockClient{}) }, 54 }, 55 "tracking: success, activity: fail": { 56 wantFail: false, 57 prepare: func() *Chrony { return prepareChronyWithMock(&mockClient{errOnActivity: true}) }, 58 }, 59 "tracking: fail, activity: success": { 60 wantFail: true, 61 prepare: func() *Chrony { return prepareChronyWithMock(&mockClient{errOnTracking: true}) }, 62 }, 63 "tracking: fail, activity: fail": { 64 wantFail: true, 65 prepare: func() *Chrony { return prepareChronyWithMock(&mockClient{errOnTracking: true}) }, 66 }, 67 "fail on creating client": { 68 wantFail: true, 69 prepare: func() *Chrony { return prepareChronyWithMock(nil) }, 70 }, 71 } 72 73 for name, test := range tests { 74 t.Run(name, func(t *testing.T) { 75 c := test.prepare() 76 77 require.True(t, c.Init()) 78 79 if test.wantFail { 80 assert.False(t, c.Check()) 81 } else { 82 assert.True(t, c.Check()) 83 } 84 }) 85 } 86 } 87 88 func TestChrony_Charts(t *testing.T) { 89 assert.Equal(t, len(charts), len(*New().Charts())) 90 } 91 92 func TestChrony_Cleanup(t *testing.T) { 93 tests := map[string]struct { 94 prepare func(c *Chrony) 95 wantClose bool 96 }{ 97 "after New": { 98 wantClose: false, 99 prepare: func(c *Chrony) {}, 100 }, 101 "after Init": { 102 wantClose: false, 103 prepare: func(c *Chrony) { c.Init() }, 104 }, 105 "after Check": { 106 wantClose: true, 107 prepare: func(c *Chrony) { c.Init(); c.Check() }, 108 }, 109 "after Collect": { 110 wantClose: true, 111 prepare: func(c *Chrony) { c.Init(); c.Collect() }, 112 }, 113 } 114 115 for name, test := range tests { 116 t.Run(name, func(t *testing.T) { 117 m := &mockClient{} 118 c := prepareChronyWithMock(m) 119 test.prepare(c) 120 121 require.NotPanics(t, c.Cleanup) 122 123 if test.wantClose { 124 assert.True(t, m.closeCalled) 125 } else { 126 assert.False(t, m.closeCalled) 127 } 128 }) 129 } 130 } 131 132 func TestChrony_Collect(t *testing.T) { 133 tests := map[string]struct { 134 prepare func() *Chrony 135 expected map[string]int64 136 }{ 137 "tracking: success, activity: success": { 138 prepare: func() *Chrony { return prepareChronyWithMock(&mockClient{}) }, 139 expected: map[string]int64{ 140 "burst_offline_sources": 3, 141 "burst_online_sources": 4, 142 "current_correction": 154872, 143 "frequency": 51051185607, 144 "last_offset": 3095, 145 "leap_status_delete_second": 0, 146 "leap_status_insert_second": 1, 147 "leap_status_normal": 0, 148 "leap_status_unsynchronised": 0, 149 "offline_sources": 2, 150 "online_sources": 8, 151 "ref_measurement_time": 63793323616, 152 "residual_frequency": -571789, 153 "rms_offset": 130089, 154 "root_delay": 59576179, 155 "root_dispersion": 1089275, 156 "skew": 41821926, 157 "stratum": 4, 158 "unresolved_sources": 1, 159 "update_interval": 1044219238281, 160 }, 161 }, 162 "tracking: success, activity: fail": { 163 prepare: func() *Chrony { return prepareChronyWithMock(&mockClient{errOnActivity: true}) }, 164 expected: map[string]int64{ 165 "current_correction": 154872, 166 "frequency": 51051185607, 167 "last_offset": 3095, 168 "leap_status_delete_second": 0, 169 "leap_status_insert_second": 1, 170 "leap_status_normal": 0, 171 "leap_status_unsynchronised": 0, 172 "ref_measurement_time": 63793323586, 173 "residual_frequency": -571789, 174 "rms_offset": 130089, 175 "root_delay": 59576179, 176 "root_dispersion": 1089275, 177 "skew": 41821926, 178 "stratum": 4, 179 "update_interval": 1044219238281, 180 }, 181 }, 182 "tracking: fail, activity: success": { 183 prepare: func() *Chrony { return prepareChronyWithMock(&mockClient{errOnTracking: true}) }, 184 expected: nil, 185 }, 186 "tracking: fail, activity: fail": { 187 prepare: func() *Chrony { return prepareChronyWithMock(&mockClient{errOnTracking: true}) }, 188 expected: nil, 189 }, 190 "fail on creating client": { 191 prepare: func() *Chrony { return prepareChronyWithMock(nil) }, 192 expected: nil, 193 }, 194 } 195 196 for name, test := range tests { 197 t.Run(name, func(t *testing.T) { 198 c := test.prepare() 199 200 require.True(t, c.Init()) 201 _ = c.Check() 202 203 collected := c.Collect() 204 copyRefMeasurementTime(collected, test.expected) 205 206 assert.Equal(t, test.expected, collected) 207 }) 208 } 209 } 210 211 func prepareChronyWithMock(m *mockClient) *Chrony { 212 c := New() 213 if m == nil { 214 c.newClient = func(_ Config) (chronyClient, error) { return nil, errors.New("mock.newClient error") } 215 } else { 216 c.newClient = func(_ Config) (chronyClient, error) { return m, nil } 217 } 218 return c 219 } 220 221 type mockClient struct { 222 errOnTracking bool 223 errOnActivity bool 224 closeCalled bool 225 } 226 227 func (m mockClient) Tracking() (*chrony.ReplyTracking, error) { 228 if m.errOnTracking { 229 return nil, errors.New("mockClient.Tracking call error") 230 } 231 reply := chrony.ReplyTracking{ 232 Tracking: chrony.Tracking{ 233 RefID: 2728380539, 234 IPAddr: net.IP("192.0.2.0"), 235 Stratum: 4, 236 LeapStatus: 1, 237 RefTime: time.Time{}, 238 CurrentCorrection: 0.00015487267228309065, 239 LastOffset: 3.0953951863921247e-06, 240 RMSOffset: 0.00013008920359425247, 241 FreqPPM: -51.051185607910156, 242 ResidFreqPPM: -0.0005717896274290979, 243 SkewPPM: 0.0418219268321991, 244 RootDelay: 0.05957617983222008, 245 RootDispersion: 0.0010892755817621946, 246 LastUpdateInterval: 1044.21923828125, 247 }, 248 } 249 return &reply, nil 250 } 251 252 func (m mockClient) Activity() (*chrony.ReplyActivity, error) { 253 if m.errOnActivity { 254 return nil, errors.New("mockClient.Activity call error") 255 } 256 reply := chrony.ReplyActivity{ 257 Activity: chrony.Activity{ 258 Online: 8, 259 Offline: 2, 260 BurstOnline: 4, 261 BurstOffline: 3, 262 Unresolved: 1, 263 }, 264 } 265 return &reply, nil 266 } 267 268 func (m *mockClient) Close() { 269 m.closeCalled = true 270 } 271 272 func copyRefMeasurementTime(dst, src map[string]int64) { 273 if _, ok := dst["ref_measurement_time"]; !ok { 274 return 275 } 276 if _, ok := src["ref_measurement_time"]; !ok { 277 return 278 } 279 dst["ref_measurement_time"] = src["ref_measurement_time"] 280 }