Skip to content

Commit b2eeb71

Browse files
committed
test: improve unit-testing
1 parent b3781d8 commit b2eeb71

File tree

10 files changed

+189
-45
lines changed

10 files changed

+189
-45
lines changed

cmd/bladectl/cmd_status.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55

66
bladeapiv1alpha1 "github.com/compute-blade-community/compute-blade-agent/api/bladeapi/v1alpha1"
77
"github.com/compute-blade-community/compute-blade-agent/pkg/hal"
8-
"github.com/compute-blade-community/compute-blade-agent/pkg/util"
98
"github.com/olekukonko/tablewriter"
109
"github.com/olekukonko/tablewriter/tw"
1110
"github.com/spf13/cobra"
@@ -68,7 +67,7 @@ func printStatusTable(bladeStatus []*bladeapiv1alpha1.StatusResponse) {
6867
activeStyle(status.StealthMode).Render(activeLabel(status.StealthMode)),
6968
activeStyle(status.IdentifyActive).Render(activeLabel(status.IdentifyActive)),
7069
activeStyle(status.CriticalActive).Render(activeLabel(status.CriticalActive)),
71-
util.OkStyle().Render(hal.PowerStatus(status.PowerStatus).String()),
70+
okStyle().Render(hal.PowerStatus(status.PowerStatus).String()),
7271
}
7372

7473
_ = tbl.Append(row)

cmd/bladectl/util.go

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ import (
44
"fmt"
55

66
"github.com/charmbracelet/lipgloss"
7-
"github.com/compute-blade-community/compute-blade-agent/pkg/util"
7+
)
8+
9+
const (
10+
ColorCritical = lipgloss.Color("#cc0000")
11+
ColorWarning = lipgloss.Color("#e69138")
12+
ColorOk = lipgloss.Color("#04B575")
813
)
914

1015
func fanSpeedOverrideLabel(automatic bool, percent uint32) string {
@@ -35,40 +40,44 @@ func activeLabel(b bool) string {
3540

3641
func speedOverrideStyle(automaticMode bool) lipgloss.Style {
3742
if automaticMode {
38-
return lipgloss.NewStyle().Foreground(util.ColorOk)
43+
return lipgloss.NewStyle().Foreground(ColorOk)
3944
}
4045

41-
return lipgloss.NewStyle().Foreground(util.ColorCritical)
46+
return lipgloss.NewStyle().Foreground(ColorCritical)
4247
}
4348

4449
func activeStyle(active bool) lipgloss.Style {
4550
if active {
46-
return lipgloss.NewStyle().Foreground(util.ColorCritical)
51+
return lipgloss.NewStyle().Foreground(ColorCritical)
4752
}
4853

49-
return lipgloss.NewStyle().Foreground(util.ColorOk)
54+
return lipgloss.NewStyle().Foreground(ColorOk)
5055
}
5156

5257
func tempStyle(temp int64, criticalTemp int64) lipgloss.Style {
53-
color := util.ColorOk
58+
color := ColorOk
5459

5560
if temp >= criticalTemp {
56-
color = util.ColorCritical
61+
color = ColorCritical
5762
} else if temp >= criticalTemp-10 {
58-
color = util.ColorWarning
63+
color = ColorWarning
5964
}
6065

6166
return lipgloss.NewStyle().Foreground(color)
6267
}
6368

6469
func rpmStyle(rpm int64) lipgloss.Style {
65-
color := util.ColorOk
70+
color := ColorOk
6671

6772
if rpm > 6000 {
68-
color = util.ColorCritical
73+
color = ColorCritical
6974
} else if rpm > 5250 {
70-
color = util.ColorWarning
75+
color = ColorWarning
7176
}
7277

7378
return lipgloss.NewStyle().Foreground(color)
7479
}
80+
81+
func okStyle() lipgloss.Style {
82+
return lipgloss.NewStyle().Foreground(ColorOk)
83+
}

internal/agent/api.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ func (a *computeBladeAgent) GetStatus(_ context.Context, _ *emptypb.Empty) (*bla
123123
return nil, err
124124
}
125125

126-
steps := a.fanController.Config().Steps
126+
steps := a.fanController.Steps()
127127
fanCurveSteps := make([]*bladeapiv1alpha1.FanCurveStep, len(steps))
128128
for idx, step := range steps {
129129
fanCurveSteps[idx] = &bladeapiv1alpha1.FanCurveStep{

pkg/fancontroller/fancontroller.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ type FanController interface {
1515
// IsAutomaticSpeed returns true if the FanSpeed is determined by the fan controller logic, or false if determined
1616
// by an FanOverrideOpts
1717
IsAutomaticSpeed() bool
18-
// Config returns a copy of the FanController Config
19-
Config() Config
18+
19+
// Steps returns the list of temperature and fan speed steps configured for the fan controller.
20+
Steps() []Step
2021
}
2122

2223
// FanController is a simple fan controller that reacts to temperature changes with a linear function
@@ -66,8 +67,8 @@ func NewLinearFanController(config Config) (FanController, humane.Error) {
6667
}, nil
6768
}
6869

69-
func (f *fanControllerLinear) Config() Config {
70-
return f.config
70+
func (f *fanControllerLinear) Steps() []Step {
71+
return f.config.Steps
7172
}
7273

7374
func (f *fanControllerLinear) Override(opts *FanOverrideOpts) {

pkg/fancontroller/fancontroller_test.go

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"testing"
55

66
"github.com/compute-blade-community/compute-blade-agent/pkg/fancontroller"
7+
"github.com/stretchr/testify/assert"
78
)
89

910
func TestFanControllerLinear_GetFanSpeed(t *testing.T) {
@@ -30,15 +31,16 @@ func TestFanControllerLinear_GetFanSpeed(t *testing.T) {
3031
{35, 60}, // Should use the maximum speed
3132
}
3233

34+
assert.Equal(t, controller.Steps(), config.Steps)
35+
3336
for _, tc := range testCases {
3437
expected := tc.expected
3538
temperature := tc.temperature
3639
t.Run("", func(t *testing.T) {
3740
t.Parallel()
3841
speed := controller.GetFanSpeedPercent(temperature)
39-
if speed != expected {
40-
t.Errorf("For temperature %.2f, expected speed %d but got %d", temperature, expected, speed)
41-
}
42+
assert.Equal(t, expected, speed)
43+
assert.True(t, controller.IsAutomaticSpeed(), "Expected fan speed to be automatic, but it was not")
4244
})
4345
}
4446
}
@@ -76,9 +78,8 @@ func TestFanControllerLinear_GetFanSpeedWithOverride(t *testing.T) {
7678
t.Run("", func(t *testing.T) {
7779
t.Parallel()
7880
speed := controller.GetFanSpeedPercent(temperature)
79-
if speed != expected {
80-
t.Errorf("For temperature %.2f, expected speed %d but got %d", temperature, expected, speed)
81-
}
81+
assert.Equal(t, expected, speed)
82+
assert.False(t, controller.IsAutomaticSpeed(), "Expected fan speed to be overridden, but it was not")
8283
})
8384
}
8485
}
@@ -127,11 +128,9 @@ func TestFanControllerLinear_ConstructionErrors(t *testing.T) {
127128
t.Run(tc.name, func(t *testing.T) {
128129
t.Parallel()
129130
_, err := fancontroller.NewLinearFanController(config)
130-
if err == nil {
131-
t.Errorf("Expected error with message '%s', but got no error", expectedErrMsg)
132-
} else if err.Error() != expectedErrMsg {
133-
t.Errorf("Expected error message '%s', but got '%s'", expectedErrMsg, err.Error())
134-
}
131+
132+
assert.NotNil(t, err, "Expected error with message '%s', but got no error", expectedErrMsg)
133+
assert.EqualError(t, err, expectedErrMsg)
135134
})
136135
}
137136
}

pkg/ledengine/ledengine_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,17 @@ func TestNewLedEngine(t *testing.T) {
123123
assert.NotNil(t, engine)
124124
}
125125

126+
func TestLedEngine_NewLedEngineWithoutClock(t *testing.T) {
127+
opts := ledengine.Options{
128+
Clock: nil,
129+
LedIdx: 0,
130+
Hal: &hal.ComputeBladeHalMock{},
131+
}
132+
133+
engine := ledengine.NewLedEngine(opts)
134+
assert.NotNil(t, engine)
135+
}
136+
126137
func Test_LedEngine_SetPattern_WhileRunning(t *testing.T) {
127138
t.Parallel()
128139

@@ -254,3 +265,29 @@ func Test_LedEngine_SetPattern_SetLedFailureInPattern(t *testing.T) {
254265
clk.AssertExpectations(t)
255266
cbMock.AssertExpectations(t)
256267
}
268+
269+
func Test_LedEngine_SetPattern_NoDelay(t *testing.T) {
270+
t.Parallel()
271+
272+
clk := util.MockClock{}
273+
clkAfterChan := make(chan time.Time)
274+
clk.On("After", time.Hour).Once().Return(clkAfterChan)
275+
276+
cbMock := hal.ComputeBladeHalMock{}
277+
cbMock.On("SetLed", hal.LedTop, led.Color{Green: 0, Blue: 0, Red: 255}).Once().Return(nil)
278+
279+
opts := ledengine.Options{
280+
Hal: &cbMock,
281+
Clock: &clk,
282+
LedIdx: 0,
283+
}
284+
285+
engine := ledengine.NewLedEngine(opts)
286+
invalidPattern := ledengine.NewStaticPattern(led.Color{Red: 255})
287+
invalidPattern.Delays = []time.Duration{}
288+
// We want to change the pattern BEFORE the engine is started
289+
t.Log("Setting pattern")
290+
err := engine.SetPattern(invalidPattern)
291+
assert.Error(t, err)
292+
assert.ErrorContains(t, err, "pattern must have at least one delay")
293+
}

pkg/util/clock_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package util_test
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/compute-blade-community/compute-blade-agent/pkg/util"
8+
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
// TestRealClock_Now ensures that RealClock.Now() returns a time close to the actual time.
13+
func TestRealClock_Now(t *testing.T) {
14+
rc := util.RealClock{}
15+
before := time.Now()
16+
got := rc.Now()
17+
after := time.Now()
18+
19+
if got.Before(before) || got.After(after) {
20+
t.Errorf("RealClock.Now() = %v, want between %v and %v", got, before, after)
21+
}
22+
}
23+
24+
// TestRealClock_After ensures that RealClock.After() returns a channel that sends after the given duration.
25+
func TestRealClock_After(t *testing.T) {
26+
rc := util.RealClock{}
27+
delay := 50 * time.Millisecond
28+
29+
start := time.Now()
30+
ch := rc.After(delay)
31+
<-ch
32+
elapsed := time.Since(start)
33+
34+
if elapsed < delay {
35+
t.Errorf("RealClock.After(%v) triggered too early after %v", delay, elapsed)
36+
}
37+
}
38+
39+
// TestMockClock_Now tests that MockClock.Now() returns the expected time and records the call.
40+
func TestMockClock_Now(t *testing.T) {
41+
mockClock := new(util.MockClock)
42+
expectedTime := time.Date(2025, time.June, 6, 12, 0, 0, 0, time.UTC)
43+
44+
mockClock.On("Now").Return(expectedTime)
45+
46+
actualTime := mockClock.Now()
47+
assert.Equal(t, expectedTime, actualTime)
48+
mockClock.AssertCalled(t, "Now")
49+
mockClock.AssertExpectations(t)
50+
}
51+
52+
// TestMockClock_After tests that MockClock.After() returns the expected channel and records the call.
53+
func TestMockClock_After(t *testing.T) {
54+
mockClock := new(util.MockClock)
55+
duration := 100 * time.Millisecond
56+
expectedChan := make(chan time.Time, 1)
57+
expectedTime := time.Now().Add(duration)
58+
expectedChan <- expectedTime
59+
60+
mockClock.On("After", duration).Return(expectedChan)
61+
62+
resultChan := mockClock.After(duration)
63+
select {
64+
case result := <-resultChan:
65+
assert.WithinDuration(t, expectedTime, result, time.Second)
66+
case <-time.After(time.Second):
67+
t.Fatal("timeout waiting for result from MockClock.After")
68+
}
69+
70+
mockClock.AssertCalled(t, "After", duration)
71+
mockClock.AssertExpectations(t)
72+
}

pkg/util/file_exist_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package util_test
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
"github.com/compute-blade-community/compute-blade-agent/pkg/util"
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestFileExists(t *testing.T) {
12+
// Create a temporary file
13+
tmpFile, err := os.CreateTemp("", "fileexists-test")
14+
assert.NoError(t, err)
15+
16+
// It should exist
17+
assert.True(t, util.FileExists(tmpFile.Name()), "Expected file to exist")
18+
19+
// Close and remove the file
20+
assert.NoError(t, tmpFile.Close())
21+
assert.NoError(t, os.Remove(tmpFile.Name()))
22+
23+
// It should not exist anymore
24+
assert.False(t, util.FileExists(tmpFile.Name()), "Expected file not to exist")
25+
}

pkg/util/host_ips_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package util_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/compute-blade-community/compute-blade-agent/pkg/util"
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestGetHostIPs_ReturnsNonLoopbackIPs(t *testing.T) {
11+
ips, err := util.GetHostIPs()
12+
assert.NoError(t, err)
13+
14+
for _, ip := range ips {
15+
assert.False(t, ip.IsLoopback(), "Should not return loopback IPs")
16+
assert.False(t, ip.IsUnspecified(), "Should not return unspecified IPs")
17+
}
18+
}

pkg/util/kv_print.go

Lines changed: 0 additions & 16 deletions
This file was deleted.

0 commit comments

Comments
 (0)