1package extensions
2
3import (
4 "context"
5 "errors"
6 "os"
7 "os/exec"
8 "path/filepath"
9 "strings"
10 "testing"
11)
12
13func TestLoadErrorsIdentifyExtensionDirectory(t *testing.T) {
14 for _, explicit := range []bool{false, true} {
15 mode := "discover"
16 if explicit {
17 mode = "explicit"
18 }
19 for _, tc := range []struct {
20 name string
21 manifest string
22 want string
23 }{
24 {"invalid-json", `{`, "parse manifest"},
25 {"missing-name", `{"exec":"missing"}`, "name is required"},
26 {"missing-exec", `{"name":"broken"}`, "exec is required"},
27 {"open-log", `{"name":"broken","exec":"missing"}`, "open log"},
28 {"missing-runtime", `{"name":"broken","exec":"zut-test-missing-runtime"}`, "failed to start"},
29 } {
30 t.Run(mode+"/"+tc.name, func(t *testing.T) {
31 home := t.TempDir()
32 dir := filepath.Join(home, "extensions", tc.name)
33 if err := os.MkdirAll(dir, 0o755); err != nil {
34 t.Fatal(err)
35 }
36 if err := os.WriteFile(filepath.Join(dir, "extension.json"), []byte(tc.manifest), 0o644); err != nil {
37 t.Fatal(err)
38 }
39 if tc.name == "open-log" {
40 if err := os.WriteFile(filepath.Join(home, "logs"), nil, 0o644); err != nil {
41 t.Fatal(err)
42 }
43 }
44 if tc.name == "missing-runtime" {
45 t.Setenv("PATH", t.TempDir())
46 }
47 mgr := New(home, "", "test", "", "", nil)
48 var errs []error
49 if explicit {
50 errs = mgr.LoadExplicit(context.Background(), []string{dir})
51 } else {
52 errs = mgr.Discover(context.Background())
53 }
54 if len(errs) != 1 {
55 t.Fatalf("errors = %v, want one", errs)
56 }
57 if got := errs[0].Error(); !strings.Contains(got, dir) || !strings.Contains(got, tc.want) {
58 t.Fatalf("error = %q, want directory %q and %q", got, dir, tc.want)
59 }
60 if tc.name == "missing-runtime" {
61 if !errors.Is(errs[0], exec.ErrNotFound) {
62 t.Fatalf("error does not wrap exec.ErrNotFound: %v", errs[0])
63 }
64 if strings.Count(errs[0].Error(), dir) != 1 {
65 t.Fatalf("directory should appear once: %v", errs[0])
66 }
67 }
68 })
69 }
70 }
71}
72
73func TestDiscoverReportsMissingRuntimeWhenExtensionCannotStart(t *testing.T) {
74 tmp := t.TempDir()
75 extDir := filepath.Join(tmp, "extensions", "missing-runtime")
76 if err := os.MkdirAll(extDir, 0o755); err != nil {
77 t.Fatal(err)
78 }
79 if err := os.WriteFile(filepath.Join(extDir, "extension.json"), []byte(`{"name":"missing-runtime","exec":"python3","language":"python"}`), 0o644); err != nil {
80 t.Fatal(err)
81 }
82
83 // Isolate PATH so this tests a missing language runtime rather than
84 // relying on a made-up executable name that could theoretically exist.
85 t.Setenv("PATH", filepath.Join(tmp, "empty-bin"))
86 mgr := New(tmp, "", "0.0.0-test", "", "", nil)
87 errs := mgr.Discover(context.Background())
88 if len(errs) != 1 {
89 t.Fatalf("discover errors = %v, want one", errs)
90 }
91 got := errs[0].Error()
92 for _, want := range []string{
93 `Extension missing-runtime failed to start.`,
94 `exec: "python3"`,
95 `declared language: "python"`,
96 "executable file not found",
97 extDir,
98 } {
99 if !strings.Contains(got, want) {
100 t.Fatalf("error = %q, want it to contain %q", got, want)
101 }
102 }
103 if strings.Count(got, extDir) != 1 {
104 t.Fatalf("directory should appear once: %q", got)
105 }
106}