diff --git a/.github/workflows/test-go-task.yml b/.github/workflows/test-go-task.yml index 1910f39..6e09309 100644 --- a/.github/workflows/test-go-task.yml +++ b/.github/workflows/test-go-task.yml @@ -99,8 +99,7 @@ jobs: - name: Run tests env: GO_MODULE_PATH: ${{ matrix.module.path }} - # run: task go:test ## TODO: refactor the tests - run: task go:test:docker + run: task go:test - name: Send unit tests coverage to Codecov if: runner.os == 'Linux' diff --git a/Taskfile.yaml b/Taskfile.yaml index bd96060..dad1f36 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -57,9 +57,3 @@ tasks: -coverprofile=coverage_unit.txt \ {{.TEST_LDFLAGS}} \ {{default .DEFAULT_GO_PACKAGES .GO_PACKAGES}} - - go:test:docker: - desc: Run the tests inside a docker image - cmds: - - docker build -f testdata/Dockerfile -t go-apt-test:latest . - - docker run --rm go-apt-test diff --git a/apt.go b/apt.go index 5f0c591..20d2171 100644 --- a/apt.go +++ b/apt.go @@ -22,6 +22,7 @@ import ( "bufio" "bytes" "fmt" + "io" "os/exec" "regexp" "strconv" @@ -85,6 +86,7 @@ func parseDpkgQueryOutput(out []byte) []*Package { // CheckForUpdates runs an apt update to retrieve new packages available // from the repositories +// NOTE: it requires root privileges to run func CheckForUpdates() (output []byte, err error) { cmd := exec.Command("apt-get", "update", "-q") return cmd.CombinedOutput() @@ -98,10 +100,18 @@ func ListUpgradable() ([]*Package, error) { if err != nil { return nil, fmt.Errorf("running apt list: %s", err) } + + res := parseListUpgradableOutput(bytes.NewReader(out)) + return res, nil +} + +func parseListUpgradableOutput(r io.Reader) []*Package { + // Example of matched line: + // xserver-xorg-core/focal-updates 2:1.20.13-1ubuntu1~20.04.20 amd64 [upgradable from: 2:1.20.13-1ubuntu1~20.04.19] re := regexp.MustCompile(`^([^ ]+) ([^ ]+) ([^ ]+)( \[upgradable from: [^\[\]]*\])?`) res := []*Package{} - scanner := bufio.NewScanner(bytes.NewReader(out)) + scanner := bufio.NewScanner(r) for scanner.Scan() { matches := re.FindAllStringSubmatch(scanner.Text(), -1) if len(matches) == 0 { @@ -120,7 +130,7 @@ func ListUpgradable() ([]*Package, error) { Architecture: matches[0][3], }) } - return res, nil + return res } // Upgrade runs the upgrade for a set of packages diff --git a/apt_test.go b/apt_test.go index 542a125..63e2209 100644 --- a/apt_test.go +++ b/apt_test.go @@ -22,12 +22,13 @@ import ( "encoding/json" "fmt" "os" + "strings" "testing" "github.com/stretchr/testify/require" ) -func TestList(t *testing.T) { +func TestParseDpkgQueryOutput(t *testing.T) { out, err := os.ReadFile("testdata/dpkg-query-output-1.txt") require.NoError(t, err, "Reading test input data") list := parseDpkgQueryOutput(out) @@ -62,9 +63,67 @@ func TestListUpgradable(t *testing.T) { require.NoError(t, err, "running List command") } -func TestCheckForUpdates(t *testing.T) { - out, err := CheckForUpdates() - require.NoError(t, err, "running CheckForUpdate command") - fmt.Printf(">>>\n%s\n<<<\n", string(out)) - fmt.Println("ERR:", err) +func TestParseListUpgradableOutput(t *testing.T) { + t.Run("edges cases", func(t *testing.T) { + tests := []struct { + name string + input string + expected []*Package + }{ + { + name: "empty input", + input: "", + expected: []*Package{}, + }, + { + name: "line not matching regex", + input: "this-is-not a-valid-line\n", + expected: []*Package{}, + }, + { + name: "upgradable package without [upgradable from]", + input: "nano/bionic-updates 2.9.3-2 amd64\n", + expected: []*Package{ + { + Name: "nano", + Status: "upgradable", + Version: "2.9.3-2", + Architecture: "amd64", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + res := parseListUpgradableOutput(strings.NewReader(tt.input)) + require.Equal(t, tt.expected, res) + }) + } + }) + + t.Run("golden file: list-upgradable.golden", func(t *testing.T) { + data, err := os.ReadFile("testdata/apt-list-upgradable.golden") + require.NoError(t, err, "Reading golden file") + result := parseListUpgradableOutput(strings.NewReader(string(data))) + + want := []*Package{ + {Name: "apt-transport-https", Status: "upgradable", Version: "2.0.11", Architecture: "all"}, + {Name: "apt-utils", Status: "upgradable", Version: "2.0.11", Architecture: "amd64"}, + {Name: "apt", Status: "upgradable", Version: "2.0.11", Architecture: "amd64"}, + {Name: "code-insiders", Status: "upgradable", Version: "1.101.0-1749657374", Architecture: "amd64"}, + {Name: "code", Status: "upgradable", Version: "1.100.3-1748872405", Architecture: "amd64"}, + {Name: "containerd.io", Status: "upgradable", Version: "1.7.27-1", Architecture: "amd64"}, + {Name: "distro-info-data", Status: "upgradable", Version: "0.43ubuntu1.18", Architecture: "all"}, + {Name: "docker-ce-cli", Status: "upgradable", Version: "5:28.1.1-1~ubuntu.20.04~focal", Architecture: "amd64"}, + {Name: "python3.12", Status: "upgradable", Version: "3.12.11-1+focal1", Architecture: "amd64"}, + {Name: "xdg-desktop-portal", Status: "upgradable", Version: "1.14.3-1~flatpak1~20.04", Architecture: "amd64"}, + {Name: "xserver-common", Status: "upgradable", Version: "2:1.20.13-1ubuntu1~20.04.20", Architecture: "all"}, + {Name: "xserver-xephyr", Status: "upgradable", Version: "2:1.20.13-1ubuntu1~20.04.20", Architecture: "amd64"}, + {Name: "xserver-xorg-core", Status: "upgradable", Version: "2:1.20.13-1ubuntu1~20.04.20", Architecture: "amd64"}, + {Name: "xserver-xorg-legacy", Status: "upgradable", Version: "2:1.20.13-1ubuntu1~20.04.20", Architecture: "amd64"}, + {Name: "xwayland", Status: "upgradable", Version: "2:1.20.13-1ubuntu1~20.04.20", Architecture: "amd64"}, + } + require.NotNil(t, result) + require.Equal(t, want, result, "Parsed result should match expected from golden file") + }) } diff --git a/testdata/Dockerfile b/testdata/Dockerfile deleted file mode 100644 index 3340a79..0000000 --- a/testdata/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM golang:1.24 - -# Install dpkg/apt tools -RUN apt-get update && apt-get install -y \ - dpkg \ - apt \ - && rm -rf /var/lib/apt/lists/* - -# Set working directory inside container -WORKDIR /app - -# Copy everything (mod files + code) -COPY . . - -# Download modules -RUN go mod download - -# Run tests in apt package -CMD ["go", "test", "-v", "./"] diff --git a/testdata/apt-list-upgradable.golden b/testdata/apt-list-upgradable.golden new file mode 100644 index 0000000..81e13e1 --- /dev/null +++ b/testdata/apt-list-upgradable.golden @@ -0,0 +1,16 @@ +Listing... Done +apt-transport-https/focal-updates,focal-updates 2.0.11 all [upgradable from: 2.0.10] +apt-utils/focal-updates 2.0.11 amd64 [upgradable from: 2.0.10] +apt/focal-updates 2.0.11 amd64 [upgradable from: 2.0.10] +code-insiders/stable 1.101.0-1749657374 amd64 [upgradable from: 1.100.0-1743745333] +code/stable 1.100.3-1748872405 amd64 [upgradable from: 1.100.2-1747260578] +containerd.io/focal 1.7.27-1 amd64 [upgradable from: 1.7.25-1] +distro-info-data/focal-updates,focal-updates 0.43ubuntu1.18 all [upgradable from: 0.43ubuntu1.16] +docker-ce-cli/focal 5:28.1.1-1~ubuntu.20.04~focal amd64 [upgradable from: 5:28.0.1-1~ubuntu.20.04~focal] +python3.12/focal 3.12.11-1+focal1 amd64 [upgradable from: 3.12.5-1+focal1] +xdg-desktop-portal/focal 1.14.3-1~flatpak1~20.04 amd64 [upgradable from: 1.6.0-1ubuntu2] +xserver-common/focal-updates,focal-updates 2:1.20.13-1ubuntu1~20.04.20 all [upgradable from: 2:1.20.13-1ubuntu1~20.04.19] +xserver-xephyr/focal-updates 2:1.20.13-1ubuntu1~20.04.20 amd64 [upgradable from: 2:1.20.13-1ubuntu1~20.04.19] +xserver-xorg-core/focal-updates 2:1.20.13-1ubuntu1~20.04.20 amd64 [upgradable from: 2:1.20.13-1ubuntu1~20.04.19] +xserver-xorg-legacy/focal-updates 2:1.20.13-1ubuntu1~20.04.20 amd64 [upgradable from: 2:1.20.13-1ubuntu1~20.04.19] +xwayland/focal-updates 2:1.20.13-1ubuntu1~20.04.20 amd64 [upgradable from: 2:1.20.13-1ubuntu1~20.04.19]