Skip to content

Commit 1c7d15d

Browse files
authored
util: add flatpak introspection utilities (#1234)
ci: run apt as sudo ci: fix flatpak remote in runner ci: flatpak install steps in runner ci: specific version of freedesktop ci: freedesktop install perms
1 parent 7268a3f commit 1c7d15d

File tree

3 files changed

+297
-0
lines changed

3 files changed

+297
-0
lines changed

.github/workflows/go-ci.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ jobs:
2828
- name: Checkout
2929
uses: actions/checkout@v4
3030

31+
- name: Install flatpak
32+
run: sudo apt update && sudo apt install -y flatpak
33+
34+
- name: Add flathub
35+
run: sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
36+
37+
- name: Add a flatpak that mutagen could support
38+
run: sudo flatpak install -y org.freedesktop.Platform/x86_64/24.08 app.zen_browser.zen
39+
3140
- name: Set up Go
3241
uses: actions/setup-go@v5
3342
with:

core/internal/utils/flatpak.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package utils
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"os/exec"
7+
"strings"
8+
)
9+
10+
func FlatpakInPath() bool {
11+
_, err := exec.LookPath("flatpak")
12+
return err == nil
13+
}
14+
15+
func FlatpakExists(name string) bool {
16+
if !FlatpakInPath() {
17+
return false
18+
}
19+
20+
cmd := exec.Command("flatpak", "info", name)
21+
err := cmd.Run()
22+
return err == nil
23+
}
24+
25+
func FlatpakSearchBySubstring(substring string) bool {
26+
if !FlatpakInPath() {
27+
return false
28+
}
29+
30+
cmd := exec.Command("flatpak", "list", "--app")
31+
var stdout bytes.Buffer
32+
cmd.Stdout = &stdout
33+
34+
if err := cmd.Run(); err != nil {
35+
return false
36+
}
37+
38+
out := stdout.String()
39+
40+
for line := range strings.SplitSeq(out, "\n") {
41+
fields := strings.Fields(line)
42+
if len(fields) > 1 {
43+
id := fields[1]
44+
idParts := strings.Split(id, ".")
45+
// We are assuming that the last part of the ID is
46+
// the package name we're looking for. This might
47+
// not always be true, some developers use arbitrary
48+
// suffixes.
49+
if len(idParts) > 0 && idParts[len(idParts)-1] == substring {
50+
cmd := exec.Command("flatpak", "info", id)
51+
err := cmd.Run()
52+
return err == nil
53+
}
54+
}
55+
}
56+
return false
57+
}
58+
59+
func FlatpakInstallationDir(name string) (string, error) {
60+
if !FlatpakInPath() {
61+
return "", errors.New("flatpak not found in PATH")
62+
}
63+
64+
cmd := exec.Command("flatpak", "info", "--show-location", name)
65+
var stdout bytes.Buffer
66+
cmd.Stdout = &stdout
67+
68+
if err := cmd.Run(); err != nil {
69+
return "", errors.New("flatpak not installed: " + name)
70+
}
71+
72+
location := strings.TrimSpace(stdout.String())
73+
if location == "" {
74+
return "", errors.New("installation directory not found for: " + name)
75+
}
76+
77+
return location, nil
78+
}
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
package utils
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"strings"
7+
"testing"
8+
)
9+
10+
func TestFlatpakInPathAvailable(t *testing.T) {
11+
result := FlatpakInPath()
12+
if !result {
13+
t.Skip("flatpak not in PATH")
14+
}
15+
if !result {
16+
t.Errorf("expected true when flatpak is in PATH")
17+
}
18+
}
19+
20+
func TestFlatpakInPathUnavailable(t *testing.T) {
21+
tempDir := t.TempDir()
22+
t.Setenv("PATH", tempDir)
23+
24+
result := FlatpakInPath()
25+
if result {
26+
t.Errorf("expected false when flatpak not in PATH, got true")
27+
}
28+
}
29+
30+
func TestFlatpakExistsValidPackage(t *testing.T) {
31+
if !FlatpakInPath() {
32+
t.Skip("flatpak not in PATH")
33+
}
34+
35+
result := FlatpakExists("com.nonexistent.package.test")
36+
if result {
37+
t.Logf("package exists (unexpected but not an error)")
38+
}
39+
}
40+
41+
func TestFlatpakExistsNoFlatpak(t *testing.T) {
42+
tempDir := t.TempDir()
43+
t.Setenv("PATH", tempDir)
44+
45+
result := FlatpakExists("any.package.name")
46+
if result {
47+
t.Errorf("expected false when flatpak not in PATH, got true")
48+
}
49+
}
50+
51+
func TestFlatpakSearchBySubstringNoFlatpak(t *testing.T) {
52+
tempDir := t.TempDir()
53+
t.Setenv("PATH", tempDir)
54+
55+
result := FlatpakSearchBySubstring("test")
56+
if result {
57+
t.Errorf("expected false when flatpak not in PATH, got true")
58+
}
59+
}
60+
61+
func TestFlatpakSearchBySubstringNonexistent(t *testing.T) {
62+
if !FlatpakInPath() {
63+
t.Skip("flatpak not in PATH")
64+
}
65+
66+
result := FlatpakSearchBySubstring("ThisIsAVeryUnlikelyPackageName12345")
67+
if result {
68+
t.Errorf("expected false for nonexistent package substring")
69+
}
70+
}
71+
72+
func TestFlatpakInstallationDirNoFlatpak(t *testing.T) {
73+
tempDir := t.TempDir()
74+
t.Setenv("PATH", tempDir)
75+
76+
_, err := FlatpakInstallationDir("any.package.name")
77+
if err == nil {
78+
t.Errorf("expected error when flatpak not in PATH")
79+
}
80+
if err != nil && !strings.Contains(err.Error(), "not found in PATH") {
81+
t.Errorf("expected 'not found in PATH' error, got: %v", err)
82+
}
83+
}
84+
85+
func TestFlatpakInstallationDirNonexistent(t *testing.T) {
86+
if !FlatpakInPath() {
87+
t.Skip("flatpak not in PATH")
88+
}
89+
90+
_, err := FlatpakInstallationDir("com.nonexistent.package.test")
91+
if err == nil {
92+
t.Errorf("expected error for nonexistent package")
93+
}
94+
if err != nil && !strings.Contains(err.Error(), "not installed") {
95+
t.Errorf("expected 'not installed' error, got: %v", err)
96+
}
97+
}
98+
99+
func TestFlatpakInstallationDirValid(t *testing.T) {
100+
if !FlatpakInPath() {
101+
t.Skip("flatpak not in PATH")
102+
}
103+
104+
// This test requires a known installed flatpak
105+
// We can't guarantee any specific flatpak is installed,
106+
// so we'll skip if we can't find a common one
107+
commonFlatpaks := []string{
108+
"org.mozilla.firefox",
109+
"org.gnome.Calculator",
110+
"org.freedesktop.Platform",
111+
}
112+
113+
var testPackage string
114+
for _, pkg := range commonFlatpaks {
115+
if FlatpakExists(pkg) {
116+
testPackage = pkg
117+
break
118+
}
119+
}
120+
121+
if testPackage == "" {
122+
t.Skip("no common flatpak packages found for testing")
123+
}
124+
125+
result, err := FlatpakInstallationDir(testPackage)
126+
if err != nil {
127+
t.Fatalf("unexpected error: %v", err)
128+
}
129+
if result == "" {
130+
t.Errorf("expected non-empty installation directory")
131+
}
132+
if !strings.Contains(result, testPackage) {
133+
t.Logf("installation directory %s doesn't contain package name (may be expected)", result)
134+
}
135+
}
136+
137+
func TestFlatpakExistsCommandFailure(t *testing.T) {
138+
if !FlatpakInPath() {
139+
t.Skip("flatpak not in PATH")
140+
}
141+
142+
// Mock a failing flatpak command through PATH interception
143+
tempDir := t.TempDir()
144+
fakeFlatpak := filepath.Join(tempDir, "flatpak")
145+
146+
script := "#!/bin/sh\nexit 1\n"
147+
err := os.WriteFile(fakeFlatpak, []byte(script), 0755)
148+
if err != nil {
149+
t.Fatalf("failed to create fake flatpak: %v", err)
150+
}
151+
152+
originalPath := os.Getenv("PATH")
153+
t.Setenv("PATH", tempDir+":"+originalPath)
154+
155+
result := FlatpakExists("test.package")
156+
if result {
157+
t.Errorf("expected false when flatpak command fails, got true")
158+
}
159+
}
160+
161+
func TestFlatpakSearchBySubstringCommandFailure(t *testing.T) {
162+
if !FlatpakInPath() {
163+
t.Skip("flatpak not in PATH")
164+
}
165+
166+
// Mock a failing flatpak command through PATH interception
167+
tempDir := t.TempDir()
168+
fakeFlatpak := filepath.Join(tempDir, "flatpak")
169+
170+
script := "#!/bin/sh\nexit 1\n"
171+
err := os.WriteFile(fakeFlatpak, []byte(script), 0755)
172+
if err != nil {
173+
t.Fatalf("failed to create fake flatpak: %v", err)
174+
}
175+
176+
originalPath := os.Getenv("PATH")
177+
t.Setenv("PATH", tempDir+":"+originalPath)
178+
179+
result := FlatpakSearchBySubstring("test")
180+
if result {
181+
t.Errorf("expected false when flatpak command fails, got true")
182+
}
183+
}
184+
185+
func TestFlatpakInstallationDirCommandFailure(t *testing.T) {
186+
if !FlatpakInPath() {
187+
t.Skip("flatpak not in PATH")
188+
}
189+
190+
// Mock a failing flatpak command through PATH interception
191+
tempDir := t.TempDir()
192+
fakeFlatpak := filepath.Join(tempDir, "flatpak")
193+
194+
script := "#!/bin/sh\nexit 1\n"
195+
err := os.WriteFile(fakeFlatpak, []byte(script), 0755)
196+
if err != nil {
197+
t.Fatalf("failed to create fake flatpak: %v", err)
198+
}
199+
200+
originalPath := os.Getenv("PATH")
201+
t.Setenv("PATH", tempDir+":"+originalPath)
202+
203+
_, err = FlatpakInstallationDir("test.package")
204+
if err == nil {
205+
t.Errorf("expected error when flatpak command fails")
206+
}
207+
if err != nil && !strings.Contains(err.Error(), "not installed") {
208+
t.Errorf("expected 'not installed' error, got: %v", err)
209+
}
210+
}

0 commit comments

Comments
 (0)