返回 DeepSeek-Reasonix
external_opener_linux_test.go
根目录 / desktop / external_opener_linux_test.go
1 //go:build !darwin && !windows
2
3 package main
4
5 import (
6 "errors"
7 "os"
8 "path/filepath"
9 "reflect"
10 "testing"
11 )
12
13 func writeLinuxDesktopFile(t *testing.T, dir, name, content string) string {
14 t.Helper()
15 path := filepath.Join(dir, name)
16 if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
17 t.Fatal(err)
18 }
19 return path
20 }
21
22 func TestParseLinuxDesktopEntryReadsDesktopEntrySectionOnly(t *testing.T) {
23 dir := t.TempDir()
24 path := writeLinuxDesktopFile(t, dir, "code.desktop", `[Desktop Entry]
25 Type=Application
26 Name=Visual Studio Code
27 Icon=vscode
28
29 [Desktop Action new-window]
30 Name=New Window
31 Icon=new-window
32 `)
33 entry, ok := parseLinuxDesktopEntry(path)
34 if !ok {
35 t.Fatal("valid desktop entry was rejected")
36 }
37 want := linuxDesktopEntry{Path: path, ID: "code", Name: "Visual Studio Code", Icon: "vscode"}
38 if entry != want {
39 t.Fatalf("parseLinuxDesktopEntry = %+v, want %+v", entry, want)
40 }
41 }
42
43 func TestParseLinuxDesktopEntryRejectsHiddenAndNonApplications(t *testing.T) {
44 dir := t.TempDir()
45 for name, content := range map[string]string{
46 "hidden.desktop": "[Desktop Entry]\nType=Application\nName=Ghost\nHidden=true\n",
47 "link.desktop": "[Desktop Entry]\nType=Link\nName=Bookmark\n",
48 "unnamed.desktop": "[Desktop Entry]\nType=Application\n",
49 "wrong-section.desktop": "[Desktop Action open]\nName=Open\n",
50 } {
51 path := writeLinuxDesktopFile(t, dir, name, content)
52 if entry, ok := parseLinuxDesktopEntry(path); ok {
53 t.Errorf("%s was accepted as %+v, want rejection", name, entry)
54 }
55 }
56 }
57
58 func TestFindLinuxDesktopEntryMatchesNormalizedAliasesThenSubstrings(t *testing.T) {
59 entries := []linuxDesktopEntry{
60 {ID: "org.gnome.Terminal", Name: "Terminal"},
61 {ID: "code", Name: "Visual Studio Code"},
62 {ID: "jetbrains-idea", Name: "IntelliJ IDEA Ultimate Edition"},
63 {ID: "kitty", Name: "kitty"},
64 }
65 if entry, ok := findLinuxDesktopEntry(entries, "org.gnome.terminal"); !ok || entry.ID != "org.gnome.Terminal" {
66 t.Fatalf("normalized id lookup = (%+v, %v), want org.gnome.Terminal", entry, ok)
67 }
68 if entry, ok := findLinuxDesktopEntry(entries, "visual-studio-code"); !ok || entry.ID != "code" {
69 t.Fatalf("normalized name lookup = (%+v, %v), want code", entry, ok)
70 }
71 if entry, ok := findLinuxDesktopEntry(entries, "intellij-idea"); !ok || entry.ID != "jetbrains-idea" {
72 t.Fatalf("substring lookup = (%+v, %v), want jetbrains-idea", entry, ok)
73 }
74 if entry, ok := findLinuxDesktopEntry(entries, "kit"); ok {
75 t.Fatalf("short alias %q unexpectedly matched %+v via substring", "kit", entry)
76 }
77 }
78
79 func TestLinuxExternalOpenerCommandPerTerminalArguments(t *testing.T) {
80 const workdir = "/tmp/reasonix workspace"
81 cases := []struct {
82 mode string
83 target string
84 want []string
85 }{
86 {"path", "/usr/bin/code", []string{"/usr/bin/code", workdir}},
87 {"ghostty", "/usr/bin/ghostty", []string{"/usr/bin/ghostty", "--working-directory=" + workdir}},
88 {"gnome-terminal", "/usr/bin/gnome-terminal", []string{"/usr/bin/gnome-terminal", "--working-directory=" + workdir}},
89 {"konsole", "/usr/bin/konsole", []string{"/usr/bin/konsole", "--workdir", workdir}},
90 {"kitty", "/usr/bin/kitty", []string{"/usr/bin/kitty", "--directory", workdir}},
91 {"alacritty", "/usr/bin/alacritty", []string{"/usr/bin/alacritty", "--working-directory", workdir}},
92 {"cwd", "/usr/bin/x-terminal-emulator", []string{"/usr/bin/x-terminal-emulator"}},
93 }
94 for _, tc := range cases {
95 spec := externalOpenerSpec{Target: tc.target, LaunchMode: tc.mode}
96 cmd, err := linuxExternalOpenerCommand(spec, workdir)
97 if err != nil {
98 t.Errorf("%s: linuxExternalOpenerCommand error = %v", tc.mode, err)
99 continue
100 }
101 if !reflect.DeepEqual(cmd.Args, tc.want) {
102 t.Errorf("%s: args = %#v, want %#v", tc.mode, cmd.Args, tc.want)
103 }
104 if cmd.Dir != workdir {
105 t.Errorf("%s: dir = %q, want workspace directory", tc.mode, cmd.Dir)
106 }
107 }
108 }
109
110 func TestLinuxExternalOpenerCommandLaunchesDesktopEntriesViaGio(t *testing.T) {
111 binDir := t.TempDir()
112 gio := filepath.Join(binDir, "gio")
113 if err := os.WriteFile(gio, []byte("#!/bin/sh\n"), 0o755); err != nil {
114 t.Fatal(err)
115 }
116 t.Setenv("PATH", binDir)
117
118 spec := externalOpenerSpec{Target: "/usr/share/applications/code.desktop", LaunchMode: "gio"}
119 cmd, err := linuxExternalOpenerCommand(spec, "/tmp/project")
120 if err != nil {
121 t.Fatalf("linuxExternalOpenerCommand error = %v", err)
122 }
123 want := []string{gio, "launch", "/usr/share/applications/code.desktop", "/tmp/project"}
124 if !reflect.DeepEqual(cmd.Args, want) {
125 t.Fatalf("gio args = %#v, want %#v", cmd.Args, want)
126 }
127
128 t.Setenv("PATH", t.TempDir())
129 if _, err := linuxExternalOpenerCommand(spec, "/tmp/project"); !errors.Is(err, os.ErrNotExist) {
130 t.Fatalf("missing gio error = %v, want os.ErrNotExist", err)
131 }
132 }
133
134 func TestResolveLinuxDesktopIconAcceptsOnlyExistingAbsoluteFiles(t *testing.T) {
135 dir := t.TempDir()
136 icon := filepath.Join(dir, "vscode.png")
137 if err := os.WriteFile(icon, []byte("png"), 0o600); err != nil {
138 t.Fatal(err)
139 }
140 if got := resolveLinuxDesktopIcon(icon); got != icon {
141 t.Fatalf("existing absolute icon = %q, want %q", got, icon)
142 }
143 if got := resolveLinuxDesktopIcon(filepath.Join(dir, "missing.png")); got != "" {
144 t.Fatalf("missing absolute icon = %q, want empty", got)
145 }
146 if got := resolveLinuxDesktopIcon(dir); got != "" {
147 t.Fatalf("directory icon = %q, want empty", got)
148 }
149 }
150
150 lines GO