Skip to content

Commit d0e377a

Browse files
authored
Merge pull request #1833 from karthikvetrivel/refactor/mock-os-release-parser
[refactor] Replace parseOSRelease env var hack with mockable function variable
2 parents ee39b45 + 2db4d0a commit d0e377a

File tree

2 files changed

+81
-10
lines changed

2 files changed

+81
-10
lines changed

controllers/object_controls.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -961,19 +961,22 @@ func TransformGPUDiscoveryPlugin(obj *appsv1.DaemonSet, config *gpuv1.ClusterPol
961961
return nil
962962
}
963963

964-
// Read and parse os-release file
965-
func parseOSRelease() (map[string]string, error) {
966-
release := map[string]string{}
964+
// parseOSRelease can be overridden in tests for mocking filesystem access.
965+
// In production, it reads and parses /host-etc/os-release.
966+
var parseOSRelease = parseOSReleaseFromFile
967967

968-
// TODO: mock this call instead
969-
if os.Getenv("UNIT_TEST") == "true" {
970-
return release, nil
971-
}
968+
// osReleaseFilePath is the path to the os-release file, configurable for testing.
969+
var osReleaseFilePath = "/host-etc/os-release"
970+
971+
// parseOSReleaseFromFile reads and parses the os-release file from the host filesystem.
972+
func parseOSReleaseFromFile() (map[string]string, error) {
973+
release := map[string]string{}
972974

973-
f, err := os.Open("/host-etc/os-release")
975+
f, err := os.Open(osReleaseFilePath)
974976
if err != nil {
975977
return nil, err
976978
}
979+
defer f.Close()
977980

978981
re := regexp.MustCompile(`^(?P<key>\w+)=(?P<value>.+)`)
979982

controllers/object_controls_test.go

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,18 @@ func getModuleRoot(dir string) (string, error) {
146146
return dir, nil
147147
}
148148

149+
// mockOSRelease returns a mock parseOSRelease function for testing.
150+
// It allows tests to simulate different operating systems without filesystem access.
151+
func mockOSRelease(osID, version string) func() (map[string]string, error) {
152+
return func() (map[string]string, error) {
153+
return map[string]string{
154+
"ID": osID,
155+
"VERSION_ID": version,
156+
"NAME": osID,
157+
}, nil
158+
}
159+
}
160+
149161
// setup creates a mock kubernetes cluster and client. Nodes are labeled with the minimum
150162
// required NFD labels to be detected as GPU nodes by the GPU Operator. A sample
151163
// ClusterPolicy resource is applied to the cluster. The ClusterPolicyController
@@ -158,8 +170,8 @@ func setup() error {
158170
boolTrue = new(bool)
159171
*boolTrue = true
160172

161-
// add env for calls that we cannot mock
162-
os.Setenv("UNIT_TEST", "true")
173+
// Mock parseOSRelease to avoid filesystem dependency in tests
174+
parseOSRelease = mockOSRelease("ubuntu", "20.04")
163175

164176
s := scheme.Scheme
165177
if err := gpuv1.AddToScheme(s); err != nil {
@@ -1393,3 +1405,59 @@ func TestService(t *testing.T) {
13931405
})
13941406
}
13951407
}
1408+
1409+
func TestParseOSReleaseFromFile(t *testing.T) {
1410+
tests := []struct {
1411+
description string
1412+
content string
1413+
expected map[string]string
1414+
}{
1415+
{
1416+
description: "quoted values",
1417+
content: `NAME="Ubuntu"` + "\n" + `VERSION_ID="20.04"`,
1418+
expected: map[string]string{"NAME": "Ubuntu", "VERSION_ID": "20.04"},
1419+
},
1420+
{
1421+
description: "unquoted values",
1422+
content: `NAME=Ubuntu` + "\n" + `ID=ubuntu`,
1423+
expected: map[string]string{"NAME": "Ubuntu", "ID": "ubuntu"},
1424+
},
1425+
{
1426+
description: "mixed quoted and unquoted",
1427+
content: `ID="rhel"` + "\n" + `VERSION_ID=8.5`,
1428+
expected: map[string]string{"ID": "rhel", "VERSION_ID": "8.5"},
1429+
},
1430+
{
1431+
description: "empty lines and comments",
1432+
content: `NAME="Ubuntu"` + "\n\n# comment\n" + `ID=ubuntu`,
1433+
expected: map[string]string{"NAME": "Ubuntu", "ID": "ubuntu"},
1434+
},
1435+
}
1436+
1437+
tempDir := t.TempDir()
1438+
1439+
// Save original value and restore after tests for future subsequent tests (if needed)
1440+
originalPath := osReleaseFilePath
1441+
defer func() { osReleaseFilePath = originalPath }()
1442+
1443+
for i, test := range tests {
1444+
t.Run(test.description, func(t *testing.T) {
1445+
testFile := filepath.Join(tempDir, fmt.Sprintf("os-release-%d", i))
1446+
err := os.WriteFile(testFile, []byte(test.content), 0600)
1447+
require.NoError(t, err)
1448+
1449+
// Override the path for this test
1450+
osReleaseFilePath = testFile
1451+
result, err := parseOSReleaseFromFile()
1452+
require.NoError(t, err)
1453+
require.Equal(t, test.expected, result)
1454+
})
1455+
}
1456+
1457+
t.Run("file not found", func(t *testing.T) {
1458+
osReleaseFilePath = "/nonexistent/path"
1459+
_, err := parseOSReleaseFromFile()
1460+
require.Error(t, err)
1461+
require.True(t, os.IsNotExist(err))
1462+
})
1463+
}

0 commit comments

Comments
 (0)