Refactor CPU usage stats test to use go:embed

Refactor the system CPU usage testing approach for improved maintainability:

1. Extract the core CPU usage parsing logic into a new `readSystemCPUUsage`
   function that accepts an io.Reader, making it more testable and modular.

2. Use go:embed directive to embed the test data file at compile time,
   eliminating runtime file operations and making tests more reliable.

3. Simplify the test by removing global variable mocking in favor of a more
   direct approach with the new reader-based function.

4. Maintain full test coverage for the long "intr" line edge case which was
   crucial for the original bug fix, while making the test more maintainable.

This change preserves the original test behavior while improving code quality,
testability, and making the tests self-contained.

Signed-off-by: Lee Gaines <leetgaines@gmail.com>
This commit is contained in:
Lee Gaines
2025-04-28 19:19:47 -04:00
parent 8d5177b229
commit 6d7a370fe5
2 changed files with 24 additions and 33 deletions

View File

@@ -6,6 +6,7 @@ import (
"bufio" "bufio"
"context" "context"
"fmt" "fmt"
"io"
"os" "os"
"strconv" "strconv"
"strings" "strings"
@@ -311,30 +312,29 @@ const (
nanoSecondsPerSecond = 1e9 nanoSecondsPerSecond = 1e9
) )
var procStatPath = "/proc/stat" // getSystemCPUUsage reads the system's CPU usage from /proc/stat and returns
// the total CPU usage in nanoseconds and the number of CPUs.
// getSystemCPUUsage returns the host system's cpu usage in
// nanoseconds and number of online CPUs. An error is returned
// if the format of the underlying file does not match.
//
// Uses /proc/stat defined by POSIX. Looks for the cpu
// statistics line and then sums up the first seven fields
// provided. See `man 5 proc` for details on specific field
// information.
func getSystemCPUUsage() (cpuUsage uint64, cpuNum uint32, _ error) { func getSystemCPUUsage() (cpuUsage uint64, cpuNum uint32, _ error) {
f, err := os.Open(procStatPath) f, err := os.Open("/proc/stat")
if err != nil { if err != nil {
return 0, 0, err return 0, 0, err
} }
defer f.Close() defer f.Close()
rdr := bufio.NewReaderSize(f, 1024) return readSystemCPUUsage(f)
}
// readSystemCPUUsage parses CPU usage information from a reader providing
// /proc/stat format data. It returns the total CPU usage in nanoseconds
// and the number of CPUs.
func readSystemCPUUsage(r io.Reader) (cpuUsage uint64, cpuNum uint32, _ error) {
rdr := bufio.NewReaderSize(r, 1024)
for { for {
data, isPartial, err := rdr.ReadLine() data, isPartial, err := rdr.ReadLine()
if err != nil { if err != nil {
return 0, 0, fmt.Errorf("error scanning '%s' file: %w", procStatPath, err) return 0, 0, fmt.Errorf("error scanning /proc/stat file: %w", err)
} }
// Assume all cpu* records are at the start of the file, like glibc: // Assume all cpu* records are at the start of the file, like glibc:
// https://github.com/bminor/glibc/blob/5d00c201b9a2da768a79ea8d5311f257871c0b43/sysdeps/unix/sysv/linux/getsysstats.c#L108-L135 // https://github.com/bminor/glibc/blob/5d00c201b9a2da768a79ea8d5311f257871c0b43/sysdeps/unix/sysv/linux/getsysstats.c#L108-L135
@@ -358,8 +358,7 @@ func getSystemCPUUsage() (cpuUsage uint64, cpuNum uint32, _ error) {
} }
totalClockTicks += v totalClockTicks += v
} }
cpuUsage = (totalClockTicks * nanoSecondsPerSecond) / cpuUsage = (totalClockTicks * nanoSecondsPerSecond) / clockTicksPerSecond
clockTicksPerSecond
} }
if '0' <= line[3] && line[3] <= '9' { if '0' <= line[3] && line[3] <= '9' {
cpuNum++ cpuNum++

View File

@@ -3,28 +3,20 @@
package daemon package daemon
import ( import (
"os" _ "embed"
"path/filepath" "strings"
"testing" "testing"
"gotest.tools/v3/assert" "gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
) )
//go:embed testdata/stat
var statData string
func TestGetSystemCPUUsageParsing(t *testing.T) { func TestGetSystemCPUUsageParsing(t *testing.T) {
dummyFilePath := filepath.Join("testdata", "stat") input := strings.NewReader(statData)
expectedCpuUsage := uint64(65647090000000) cpuUsage, cpuNum, _ := readSystemCPUUsage(input)
expectedCpuNum := uint32(128) assert.Check(t, is.Equal(cpuUsage, uint64(65647090000000)))
assert.Check(t, is.Equal(cpuNum, uint32(128)))
origStatPath := procStatPath
procStatPath = dummyFilePath
defer func() { procStatPath = origStatPath }()
_, err := os.Stat(dummyFilePath)
assert.NilError(t, err)
cpuUsage, cpuNum, err := getSystemCPUUsage()
assert.Equal(t, cpuUsage, expectedCpuUsage)
assert.Equal(t, cpuNum, expectedCpuNum)
assert.NilError(t, err)
} }