Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ linters:
default: all
disable:
- depguard
- paralleltest
- tagliatelle
- testpackage
settings:
Expand Down
129 changes: 129 additions & 0 deletions internal/os/api/exit_code_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright 2026 New Relic Corporation. All rights reserved.

Check failure on line 1 in internal/os/api/exit_code_test.go

View workflow job for this annotation

GitHub Actions / linter-windows / Lint tests

File is not properly formatted (gofmt)
// SPDX-License-Identifier: Apache-2.0

package api

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestExitCodeErr_Error(t *testing.T) {
tests := []struct {
name string
exitCode int
expected string
}{
{
name: "exit code 0",
exitCode: 0,
expected: "returned non zero exit: 0",
},
{
name: "exit code 1",
exitCode: 1,
expected: "returned non zero exit: 1",
},
{
name: "exit code 3 (restart)",
exitCode: 3,
expected: "returned non zero exit: 3",
},
{
name: "negative exit code",
exitCode: -1,
expected: "returned non zero exit: -1",
},
{
name: "large exit code",
exitCode: 255,
expected: "returned non zero exit: 255",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := &ExitCodeErr{exitCode: tt.exitCode}
assert.Equal(t, tt.expected, err.Error())
})
}
}

func TestExitCodeErr_ExitCode(t *testing.T) {
tests := []struct {
name string
exitCode int
}{
{
name: "exit code success",
exitCode: ExitCodeSuccess,
},
{
name: "exit code error",
exitCode: ExitCodeError,
},
{
name: "exit code restart",
exitCode: ExitCodeRestart,
},
{
name: "custom exit code",
exitCode: 42,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := &ExitCodeErr{exitCode: tt.exitCode}
assert.Equal(t, tt.exitCode, err.ExitCode())
})
}
}

func TestNewExitCodeErr(t *testing.T) {
tests := []struct {
name string
exitCode int
}{
{
name: "create with success code",
exitCode: ExitCodeSuccess,
},
{
name: "create with error code",
exitCode: ExitCodeError,
},
{
name: "create with restart code",
exitCode: ExitCodeRestart,
},
{
name: "create with negative code",
exitCode: -1,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := NewExitCodeErr(tt.exitCode)
assert.NotNil(t, err)
assert.Equal(t, tt.exitCode, err.ExitCode())
})
}
}

func TestExitCodeConstants(t *testing.T) {
assert.Equal(t, 0, ExitCodeSuccess)
assert.Equal(t, 1, ExitCodeError)
assert.Equal(t, 3, ExitCodeRestart)
}

func TestCheckExitCode_NilError(t *testing.T) {
result := CheckExitCode(nil)
assert.Equal(t, ExitCodeSuccess, result)
}

func TestExitCodeErr_ImplementsError(_ *testing.T) {
var _ error = (*ExitCodeErr)(nil)
}
217 changes: 217 additions & 0 deletions internal/os/fs/fs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
// Copyright 2026 New Relic Corporation. All rights reserved.

Check failure on line 1 in internal/os/fs/fs_test.go

View workflow job for this annotation

GitHub Actions / linter-windows / Lint tests

File is not properly formatted (gofmt)
// SPDX-License-Identifier: Apache-2.0

package fs

import (
"os"
"path/filepath"
"regexp"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestReadFirstLine(t *testing.T) {
tests := []struct {
name string
content string
expected string
expectError bool
}{
{
name: "single line",
content: "Hello World",
expected: "Hello World",
expectError: false,
},
{
name: "multiple lines returns first",
content: "First Line\nSecond Line\nThird Line",
expected: "First Line",
expectError: false,
},
{
name: "trims whitespace",
content: " Trimmed Content \nSecond Line",
expected: "Trimmed Content",
expectError: false,
},
{
name: "empty file",
content: "",
expected: "",
expectError: false,
},
{
name: "only newline",
content: "\n",
expected: "",
expectError: false,
},
{
name: "tabs and spaces",
content: "\t Hello \t\nWorld",
expected: "Hello",
expectError: false,
},
}

for _, tt := range tests { //nolint:varnamelen
t.Run(tt.name, func(t *testing.T) {
tmpFile := createTempFile(t, tt.content)
defer os.Remove(tmpFile)

result, err := ReadFirstLine(tmpFile)
if tt.expectError {
require.Error(t, err)
} else {
require.NoError(t, err)
assert.Equal(t, tt.expected, result)
}
})
}
}

func TestReadFirstLine_FileNotFound(t *testing.T) {
result, err := ReadFirstLine("/nonexistent/path/file.txt")
require.Error(t, err)
assert.Equal(t, "unknown", result)
assert.Contains(t, err.Error(), "cannot open file")
}

func TestReadFileFieldMatching(t *testing.T) {
tests := []struct {
name string
content string
pattern string
expected string
expectError bool
}{
{
name: "match on first line",
content: "VERSION=1.2.3\nNAME=test",
pattern: `VERSION=(.+)`,
expected: "1.2.3",
expectError: false,
},
{
name: "match on second line",
content: "NAME=test\nVERSION=1.2.3",
pattern: `VERSION=(.+)`,
expected: "1.2.3",
expectError: false,
},
{
name: "match with quotes",
content: `NAME="Ubuntu"\nVERSION="20.04"`,
pattern: `NAME="([^"]+)"`,
expected: "Ubuntu",
expectError: false,
},
{
name: "no match returns unknown",
content: "FOO=bar\nBAZ=qux",
pattern: `VERSION=(.+)`,
expected: "unknown",
expectError: false,
},
{
name: "complex regex",
content: "Red Hat Enterprise Linux release 8.4 (Ootpa)",
pattern: `release (\d+\.\d+)`,
expected: "8.4",
expectError: false,
},
{
name: "empty file returns unknown",
content: "",
pattern: `(.+)`,
expected: "unknown",
expectError: false,
},
}

for _, tt := range tests { //nolint:varnamelen
t.Run(tt.name, func(t *testing.T) {
tmpFile := createTempFile(t, tt.content)

defer os.Remove(tmpFile)

re := regexp.MustCompile(tt.pattern)

result, err := ReadFileFieldMatching(tmpFile, re)
if tt.expectError {
assert.Error(t, err)
} else {
// Note: err may still be nil even when result is "unknown"
assert.Equal(t, tt.expected, result)
}
})
}
}

func TestReadFileFieldMatching_FileNotFound(t *testing.T) {
re := regexp.MustCompile(`(.+)`)
result, err := ReadFileFieldMatching("/nonexistent/path/file.txt", re)
require.Error(t, err)
assert.Equal(t, "unknown", result)
assert.Contains(t, err.Error(), "cannot open file")
}

func TestReadFileFieldMatching_MultipleCaptures(t *testing.T) {
content := "VERSION_ID=8.4"

tmpFile := createTempFile(t, content)
defer os.Remove(tmpFile)

// Pattern with multiple capture groups - should return first capture
re := regexp.MustCompile(`VERSION_ID=(\d+)\.(\d+)`)
result, err := ReadFileFieldMatching(tmpFile, re)
require.NoError(t, err)
assert.Equal(t, "8", result) // First capture group
}

func TestReadFileFieldMatching_LinuxReleaseFiles(t *testing.T) {
t.Run("os-release NAME", func(t *testing.T) {
tmpFile := createTempFile(t, `NAME="Ubuntu"\nVERSION="20.04.3 LTS (Focal Fossa)"`)
defer os.Remove(tmpFile)

re := regexp.MustCompile(`NAME="?([^"\n]+)"?`)
result, err := ReadFileFieldMatching(tmpFile, re)
require.NoError(t, err)
assert.Equal(t, "Ubuntu", result)
})

t.Run("centos-release", func(t *testing.T) {
tmpFile := createTempFile(t, "CentOS Linux release 7.9.2009 (Core)")
defer os.Remove(tmpFile)

re := regexp.MustCompile(`release (\d+\.\d+)`)
result, err := ReadFileFieldMatching(tmpFile, re)
require.NoError(t, err)
assert.Equal(t, "7.9", result)
})

t.Run("redhat-release", func(t *testing.T) {
tmpFile := createTempFile(t, "Red Hat Enterprise Linux release 8.4 (Ootpa)")
defer os.Remove(tmpFile)

re := regexp.MustCompile(`release (\d+\.\d+)`)
result, err := ReadFileFieldMatching(tmpFile, re)
require.NoError(t, err)
assert.Equal(t, "8.4", result)
})
}

// createTempFile creates a temporary file with the given content and returns its path.
func createTempFile(t *testing.T, content string) string {
t.Helper()
tmpDir := t.TempDir()
tmpFile := filepath.Join(tmpDir, "testfile")
err := os.WriteFile(tmpFile, []byte(content), 0o600)
require.NoError(t, err)

return tmpFile
}
Loading
Loading