master
go 196 lines 4.97 KB
Raw
1 package jmxbridge
2
3 import (
4 "bufio"
5 "context"
6 "encoding/json"
7 "io"
8 "os"
9 "sync"
10 "testing"
11 "time"
12 )
13
14 type testLogger struct{}
15
16 func (testLogger) Debugf(string, ...any) {}
17 func (testLogger) Infof(string, ...any) {}
18 func (testLogger) Warningf(string, ...any) {}
19 func (testLogger) Errorf(string, ...any) {}
20
21 type fakeProcess struct {
22 stdinR *io.PipeReader
23 stdinW *io.PipeWriter
24 stdoutR *io.PipeReader
25 stdoutW *io.PipeWriter
26 stderrR *io.PipeReader
27 stderrW *io.PipeWriter
28 done chan struct{}
29 handler func(map[string]any) Response
30 }
31
32 func newFakeProcess(handler func(map[string]any) Response) *fakeProcess {
33 stdinR, stdinW := io.Pipe()
34 stdoutR, stdoutW := io.Pipe()
35 stderrR, stderrW := io.Pipe()
36 return &fakeProcess{
37 stdinR: stdinR,
38 stdinW: stdinW,
39 stdoutR: stdoutR,
40 stdoutW: stdoutW,
41 stderrR: stderrR,
42 stderrW: stderrW,
43 done: make(chan struct{}),
44 handler: handler,
45 }
46 }
47
48 func (p *fakeProcess) Start() error {
49 go p.run()
50 return nil
51 }
52
53 func (p *fakeProcess) run() {
54 defer close(p.done)
55 defer p.stdoutW.Close()
56 defer p.stderrW.Close()
57 scanner := bufio.NewScanner(p.stdinR)
58 for scanner.Scan() {
59 line := scanner.Text()
60 var cmd map[string]any
61 if err := json.Unmarshal([]byte(line), &cmd); err != nil {
62 continue
63 }
64 resp := p.handler(cmd)
65 payload, _ := json.Marshal(resp)
66 _, _ = p.stdoutW.Write(append(payload, '\n'))
67 }
68 }
69
70 func (p *fakeProcess) Stdin() io.WriteCloser { return p.stdinW }
71 func (p *fakeProcess) Stdout() io.ReadCloser { return p.stdoutR }
72 func (p *fakeProcess) Stderr() io.ReadCloser { return p.stderrR }
73 func (p *fakeProcess) Wait() error { <-p.done; return nil }
74
75 func TestClientStartAndSend(t *testing.T) {
76 var call int
77 handler := func(cmd map[string]any) Response {
78 call++
79 if call == 1 {
80 return Response{Status: "OK"}
81 }
82 return Response{Status: "OK", Data: map[string]any{"value": 123}}
83 }
84
85 var procMu sync.Mutex
86 var proc *fakeProcess
87
88 factory := func(ctx context.Context, javaPath, jarPath string) (process, error) {
89 procMu.Lock()
90 defer procMu.Unlock()
91 proc = newFakeProcess(handler)
92 return proc, nil
93 }
94
95 client, err := NewClient(Config{JarPath: "helper.jar"}, testLogger{}, WithProcessFactory(factory))
96 if err != nil {
97 t.Fatalf("NewClient failed: %v", err)
98 }
99
100 if err := client.Start(context.Background(), Command{"command": "INIT"}); err != nil {
101 t.Fatalf("Start failed: %v", err)
102 }
103
104 resp, err := client.Send(context.Background(), Command{"command": "SCRAPE"})
105 if err != nil {
106 t.Fatalf("Send failed: %v", err)
107 }
108
109 if value := resp.Data["value"]; value != float64(123) {
110 t.Fatalf("unexpected response data: %#v", resp.Data)
111 }
112
113 client.Shutdown()
114 }
115
116 func TestClientErrorStatus(t *testing.T) {
117 var call int
118 handler := func(cmd map[string]any) Response {
119 call++
120 if call == 1 {
121 return Response{Status: "OK"}
122 }
123 return Response{Status: "ERROR", Message: "boom"}
124 }
125
126 factory := func(ctx context.Context, javaPath, jarPath string) (process, error) {
127 return newFakeProcess(handler), nil
128 }
129
130 client, err := NewClient(Config{JarPath: "helper.jar"}, testLogger{}, WithProcessFactory(factory))
131 if err != nil {
132 t.Fatalf("NewClient failed: %v", err)
133 }
134
135 if err := client.Start(context.Background(), Command{"command": "INIT"}); err != nil {
136 t.Fatalf("Start failed: %v", err)
137 }
138
139 if _, err := client.Send(context.Background(), Command{"command": "SCRAPE"}); err == nil {
140 t.Fatalf("expected error status failure")
141 }
142
143 client.Shutdown()
144 }
145
146 func TestClientCancellation(t *testing.T) {
147 handler := func(cmd map[string]any) Response {
148 time.Sleep(200 * time.Millisecond)
149 return Response{Status: "OK"}
150 }
151
152 factory := func(ctx context.Context, javaPath, jarPath string) (process, error) {
153 return newFakeProcess(handler), nil
154 }
155
156 client, err := NewClient(Config{JarPath: "helper.jar"}, testLogger{}, WithProcessFactory(factory))
157 if err != nil {
158 t.Fatalf("NewClient failed: %v", err)
159 }
160
161 if err := client.Start(context.Background(), Command{"command": "INIT"}); err != nil {
162 t.Fatalf("Start failed: %v", err)
163 }
164
165 ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
166 defer cancel()
167
168 if _, err := client.Send(ctx, Command{"command": "SCRAPE"}); err == nil {
169 t.Fatalf("expected cancellation error")
170 }
171
172 client.Shutdown()
173 }
174
175 func TestClientWritesJar(t *testing.T) {
176 jarData := []byte("fake jar contents")
177 client, err := NewClient(Config{JarData: jarData, JarFileName: "helper.jar"}, testLogger{}, WithProcessFactory(func(ctx context.Context, javaPath, jarPath string) (process, error) {
178 if _, err := os.Stat(jarPath); err != nil {
179 t.Fatalf("jar file not written: %v", err)
180 }
181 return newFakeProcess(func(cmd map[string]any) Response { return Response{Status: "OK"} }), nil
182 }))
183 if err != nil {
184 t.Fatalf("NewClient failed: %v", err)
185 }
186
187 if err := client.Start(context.Background(), Command{"command": "INIT"}); err != nil {
188 t.Fatalf("Start failed: %v", err)
189 }
190
191 path := client.jarPath
192 client.Shutdown()
193 if _, err := os.Stat(path); err == nil {
194 t.Fatalf("jar file should be removed on shutdown")
195 }
196 }