@termwright/probe-tview 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -89
- package/assets/tcell_marker_windows.go.txt +80 -0
- package/assets/tview_probe.go.txt +1368 -0
- package/assets/tview_probe_test.go.txt +483 -0
- package/dist/index.d.ts +16 -32
- package/dist/index.js +118 -116
- package/dist/index.js.map +1 -1
- package/package.json +9 -8
- package/upstream-patches/tview/v0.42.0/add/termwright_probe.go +0 -876
- package/upstream-patches/tview/v0.42.0/add/termwright_probe_test.go +0 -428
- package/upstream-patches/tview/v0.42.0/manifest.json +0 -42
- package/upstream-patches/tview/v0.42.0/patches/application.go.patch +0 -14
- package/upstream-patches/tview/v0.42.0/patches/go.mod.patch +0 -15
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
package tview
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"errors"
|
|
5
|
+
"reflect"
|
|
6
|
+
"runtime/debug"
|
|
7
|
+
"sync/atomic"
|
|
8
|
+
"testing"
|
|
9
|
+
|
|
10
|
+
"github.com/gdamore/tcell/v2"
|
|
11
|
+
|
|
12
|
+
"github.com/gorce-ai/termwright/clients/go/protocol"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
type termwrightControlledPublicationSink struct {
|
|
16
|
+
ready chan struct{}
|
|
17
|
+
failures atomic.Int32
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
func (s *termwrightControlledPublicationSink) TryPublish(*protocol.Snapshot) (string, error) {
|
|
21
|
+
return "", protocol.ErrPublicationQueueFull
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func (s *termwrightControlledPublicationSink) ReadyAfterDrop() <-chan struct{} { return s.ready }
|
|
25
|
+
func (s *termwrightControlledPublicationSink) Fail(string, string) { s.failures.Add(1) }
|
|
26
|
+
|
|
27
|
+
type termwrightTestScreen struct {
|
|
28
|
+
tcell.Screen
|
|
29
|
+
onShow func()
|
|
30
|
+
onFini func()
|
|
31
|
+
finalized atomic.Bool
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
func (s *termwrightTestScreen) Show() {
|
|
35
|
+
if s.onShow != nil {
|
|
36
|
+
s.onShow()
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
s.Screen.Show()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
func (s *termwrightTestScreen) Fini() {
|
|
43
|
+
if !s.finalized.CompareAndSwap(false, true) {
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
if s.onFini != nil {
|
|
47
|
+
s.onFini()
|
|
48
|
+
}
|
|
49
|
+
s.Screen.Fini()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
func termwrightNewTestScreen(t *testing.T) *termwrightTestScreen {
|
|
53
|
+
t.Helper()
|
|
54
|
+
simulation := tcell.NewSimulationScreen("UTF-8")
|
|
55
|
+
if err := simulation.Init(); err != nil {
|
|
56
|
+
t.Fatal(err)
|
|
57
|
+
}
|
|
58
|
+
screen := &termwrightTestScreen{Screen: simulation}
|
|
59
|
+
t.Cleanup(screen.Fini)
|
|
60
|
+
return screen
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
func termwrightDecorator(application *Application, screen tcell.Screen, commit func(Primitive, tcell.Screen)) *termwrightScreen {
|
|
64
|
+
decorated := &termwrightScreen{
|
|
65
|
+
Screen: screen, application: application, commit: commit,
|
|
66
|
+
}
|
|
67
|
+
decorated.previousBefore = application.beforeDraw
|
|
68
|
+
decorated.previousAfter = application.afterDraw
|
|
69
|
+
decorated.beforeHook = decorated.beforeDraw
|
|
70
|
+
decorated.afterHook = decorated.afterDraw
|
|
71
|
+
application.beforeDraw = decorated.beforeHook
|
|
72
|
+
application.afterDraw = decorated.afterHook
|
|
73
|
+
application.screen = decorated
|
|
74
|
+
return decorated
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
type termwrightIntermediateShowPrimitive struct{ *Box }
|
|
78
|
+
|
|
79
|
+
func (p *termwrightIntermediateShowPrimitive) Draw(screen tcell.Screen) {
|
|
80
|
+
x, y, _, _ := p.GetRect()
|
|
81
|
+
screen.SetContent(x, y, 'a', nil, tcell.StyleDefault)
|
|
82
|
+
screen.Show()
|
|
83
|
+
screen.SetContent(x+1, y, 'b', nil, tcell.StyleDefault)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
func TestTermwrightScreenIgnoresIntermediateCustomPrimitiveShow(t *testing.T) {
|
|
87
|
+
application := NewApplication()
|
|
88
|
+
root := &termwrightIntermediateShowPrimitive{Box: NewBox()}
|
|
89
|
+
application.SetRoot(root, true)
|
|
90
|
+
underlying := termwrightNewTestScreen(t)
|
|
91
|
+
shows := 0
|
|
92
|
+
underlying.onShow = func() { shows++ }
|
|
93
|
+
commits := 0
|
|
94
|
+
termwrightDecorator(application, underlying, func(Primitive, tcell.Screen) { commits++ })
|
|
95
|
+
|
|
96
|
+
application.draw()
|
|
97
|
+
if shows != 2 || commits != 1 {
|
|
98
|
+
t.Fatalf("custom draw shows/commits = %d/%d, want 2/1", shows, commits)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
func TestTermwrightScreenIgnoresShowsInsideLifecycleHooks(t *testing.T) {
|
|
103
|
+
for _, test := range []struct {
|
|
104
|
+
name string
|
|
105
|
+
set func(*Application)
|
|
106
|
+
}{
|
|
107
|
+
{name: "before-short-circuit", set: func(application *Application) {
|
|
108
|
+
application.SetBeforeDrawFunc(func(screen tcell.Screen) bool { screen.Show(); return true })
|
|
109
|
+
}},
|
|
110
|
+
{name: "after", set: func(application *Application) {
|
|
111
|
+
application.SetAfterDrawFunc(func(screen tcell.Screen) { screen.Show() })
|
|
112
|
+
}},
|
|
113
|
+
} {
|
|
114
|
+
t.Run(test.name, func(t *testing.T) {
|
|
115
|
+
application := NewApplication()
|
|
116
|
+
application.SetRoot(NewBox(), true)
|
|
117
|
+
test.set(application)
|
|
118
|
+
underlying := termwrightNewTestScreen(t)
|
|
119
|
+
shows := 0
|
|
120
|
+
underlying.onShow = func() { shows++ }
|
|
121
|
+
commits := 0
|
|
122
|
+
termwrightDecorator(application, underlying, func(Primitive, tcell.Screen) { commits++ })
|
|
123
|
+
application.draw()
|
|
124
|
+
if shows != 2 || commits != 1 {
|
|
125
|
+
t.Fatalf("hook shows/commits = %d/%d, want 2/1", shows, commits)
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
func TestTermwrightScreenFailsClosedWhenLifecycleHookIsDisplaced(t *testing.T) {
|
|
132
|
+
application := NewApplication()
|
|
133
|
+
application.SetRoot(NewBox(), true)
|
|
134
|
+
underlying := termwrightNewTestScreen(t)
|
|
135
|
+
commits := 0
|
|
136
|
+
decorated := termwrightDecorator(application, underlying, func(Primitive, tcell.Screen) { commits++ })
|
|
137
|
+
failures := 0
|
|
138
|
+
decorated.fail = func(code, message string) {
|
|
139
|
+
if code != "adapter-guarantee-violation" || message == "" {
|
|
140
|
+
t.Fatalf("unexpected failure %q: %q", code, message)
|
|
141
|
+
}
|
|
142
|
+
failures++
|
|
143
|
+
}
|
|
144
|
+
application.SetAfterDrawFunc(func(tcell.Screen) {})
|
|
145
|
+
|
|
146
|
+
application.draw()
|
|
147
|
+
if failures != 1 || commits != 0 || !decorated.failed.Load() {
|
|
148
|
+
t.Fatalf("displaced hook failures/commits/failed = %d/%d/%v, want 1/0/true", failures, commits, decorated.failed.Load())
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
func TestTermwrightDependencyVersionAcceptsWorkspaceAndLocalReplacement(t *testing.T) {
|
|
153
|
+
for _, test := range []struct {
|
|
154
|
+
module *debug.Module
|
|
155
|
+
want string
|
|
156
|
+
}{
|
|
157
|
+
{module: &debug.Module{Version: "v0.42.0", Replace: &debug.Module{Path: "/workspace/tview"}}, want: "v0.42.0"},
|
|
158
|
+
{module: &debug.Module{Version: "(devel)", Replace: &debug.Module{Path: "/workspace/tview"}}, want: "capability-local"},
|
|
159
|
+
{module: &debug.Module{Replace: &debug.Module{Path: "/workspace/tview"}}, want: "capability-local"},
|
|
160
|
+
} {
|
|
161
|
+
if got := termwrightDependencyVersion(test.module); got != test.want {
|
|
162
|
+
t.Fatalf("dependency version = %q, want %q", got, test.want)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
func TestTermwrightScreenCommitsAfterTheOnlyUnderlyingShow(t *testing.T) {
|
|
168
|
+
application := NewApplication()
|
|
169
|
+
root := NewBox()
|
|
170
|
+
application.SetRoot(root, true)
|
|
171
|
+
events := make([]string, 0, 2)
|
|
172
|
+
underlying := termwrightNewTestScreen(t)
|
|
173
|
+
underlying.onShow = func() { events = append(events, "show") }
|
|
174
|
+
termwrightDecorator(application, underlying, func(got Primitive, screen tcell.Screen) {
|
|
175
|
+
if got != root || screen != underlying {
|
|
176
|
+
t.Fatalf("commit observed root/screen %T/%T", got, screen)
|
|
177
|
+
}
|
|
178
|
+
events = append(events, "commit-marker")
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
application.draw()
|
|
182
|
+
if want := []string{"show", "commit-marker"}; !reflect.DeepEqual(events, want) {
|
|
183
|
+
t.Fatalf("frame order = %v, want %v", events, want)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
func TestTermwrightScreenObservesBeforeDrawShortCircuit(t *testing.T) {
|
|
188
|
+
application := NewApplication()
|
|
189
|
+
root := NewBox()
|
|
190
|
+
application.SetRoot(root, true)
|
|
191
|
+
events := make([]string, 0, 3)
|
|
192
|
+
application.SetBeforeDrawFunc(func(tcell.Screen) bool {
|
|
193
|
+
events = append(events, "before")
|
|
194
|
+
return true
|
|
195
|
+
})
|
|
196
|
+
underlying := termwrightNewTestScreen(t)
|
|
197
|
+
underlying.onShow = func() { events = append(events, "show") }
|
|
198
|
+
termwrightDecorator(application, underlying, func(got Primitive, _ tcell.Screen) {
|
|
199
|
+
if got != root {
|
|
200
|
+
t.Fatalf("commit root = %T, want initial root", got)
|
|
201
|
+
}
|
|
202
|
+
events = append(events, "commit-marker")
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
application.draw()
|
|
206
|
+
if want := []string{"before", "show", "commit-marker"}; !reflect.DeepEqual(events, want) {
|
|
207
|
+
t.Fatalf("short-circuit order = %v, want %v", events, want)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
func TestTermwrightScreenReadsDynamicRootAfterEachShow(t *testing.T) {
|
|
212
|
+
application := NewApplication()
|
|
213
|
+
first := NewBox()
|
|
214
|
+
second := NewTextView().SetText("second")
|
|
215
|
+
application.SetRoot(first, true)
|
|
216
|
+
underlying := termwrightNewTestScreen(t)
|
|
217
|
+
roots := make([]Primitive, 0, 2)
|
|
218
|
+
termwrightDecorator(application, underlying, func(root Primitive, _ tcell.Screen) {
|
|
219
|
+
roots = append(roots, root)
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
application.draw()
|
|
223
|
+
application.SetRoot(second, true)
|
|
224
|
+
application.draw()
|
|
225
|
+
if want := []Primitive{first, second}; !reflect.DeepEqual(roots, want) {
|
|
226
|
+
t.Fatalf("committed roots = %v, want %v", roots, want)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
func TestTermwrightScreensDoNotSerializeIndependentApplications(t *testing.T) {
|
|
231
|
+
firstApplication := NewApplication()
|
|
232
|
+
secondApplication := NewApplication()
|
|
233
|
+
firstApplication.root = NewBox()
|
|
234
|
+
secondApplication.root = NewBox()
|
|
235
|
+
firstUnderlying := termwrightNewTestScreen(t)
|
|
236
|
+
secondUnderlying := termwrightNewTestScreen(t)
|
|
237
|
+
entered := make(chan struct{})
|
|
238
|
+
release := make(chan struct{})
|
|
239
|
+
firstUnderlying.onShow = func() {
|
|
240
|
+
close(entered)
|
|
241
|
+
<-release
|
|
242
|
+
}
|
|
243
|
+
secondShown := false
|
|
244
|
+
secondUnderlying.onShow = func() { secondShown = true }
|
|
245
|
+
first := termwrightDecorator(firstApplication, firstUnderlying, nil)
|
|
246
|
+
second := termwrightDecorator(secondApplication, secondUnderlying, nil)
|
|
247
|
+
done := make(chan struct{})
|
|
248
|
+
go func() {
|
|
249
|
+
first.Show()
|
|
250
|
+
close(done)
|
|
251
|
+
}()
|
|
252
|
+
<-entered
|
|
253
|
+
second.Show()
|
|
254
|
+
if !secondShown {
|
|
255
|
+
t.Fatal("second application was serialized behind the first")
|
|
256
|
+
}
|
|
257
|
+
close(release)
|
|
258
|
+
<-done
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
func TestTermwrightIdleShowDoesNotReadHooksBeingConfigured(t *testing.T) {
|
|
262
|
+
application := NewApplication()
|
|
263
|
+
underlying := termwrightNewTestScreen(t)
|
|
264
|
+
underlying.onShow = func() {}
|
|
265
|
+
decorated := termwrightDecorator(application, underlying, nil)
|
|
266
|
+
done := make(chan struct{})
|
|
267
|
+
go func() {
|
|
268
|
+
defer close(done)
|
|
269
|
+
for index := 0; index < 1000; index++ {
|
|
270
|
+
application.SetAfterDrawFunc(func(tcell.Screen) {})
|
|
271
|
+
}
|
|
272
|
+
}()
|
|
273
|
+
for index := 0; index < 1000; index++ {
|
|
274
|
+
decorated.Show()
|
|
275
|
+
}
|
|
276
|
+
<-done
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
func TestTermwrightScreenFailsClosedOnReentrantShow(t *testing.T) {
|
|
280
|
+
application := NewApplication()
|
|
281
|
+
application.root = NewBox()
|
|
282
|
+
underlying := termwrightNewTestScreen(t)
|
|
283
|
+
var decorated *termwrightScreen
|
|
284
|
+
var failures atomic.Int32
|
|
285
|
+
commits := 0
|
|
286
|
+
decorated = termwrightDecorator(application, underlying, func(Primitive, tcell.Screen) {
|
|
287
|
+
commits++
|
|
288
|
+
})
|
|
289
|
+
decorated.fail = func(code, message string) {
|
|
290
|
+
if code != "adapter-guarantee-violation" || message == "" {
|
|
291
|
+
t.Fatalf("unexpected failure %q: %q", code, message)
|
|
292
|
+
}
|
|
293
|
+
failures.Add(1)
|
|
294
|
+
}
|
|
295
|
+
underlying.onShow = func() { decorated.Show() }
|
|
296
|
+
|
|
297
|
+
decorated.Show()
|
|
298
|
+
if failures.Load() != 1 || commits != 0 {
|
|
299
|
+
t.Fatalf("reentrant Show failures/commits = %d/%d, want 1/0", failures.Load(), commits)
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
func TestTermwrightScreenCleanupRestoresOnlyItsOwnInstallation(t *testing.T) {
|
|
304
|
+
t.Run("restores", func(t *testing.T) {
|
|
305
|
+
application := NewApplication()
|
|
306
|
+
underlying := termwrightNewTestScreen(t)
|
|
307
|
+
decorated := termwrightDecorator(application, underlying, nil)
|
|
308
|
+
decorated.detach()
|
|
309
|
+
if application.screen != underlying {
|
|
310
|
+
t.Fatalf("cleanup restored %T, want underlying screen", application.screen)
|
|
311
|
+
}
|
|
312
|
+
})
|
|
313
|
+
t.Run("later replacement wins", func(t *testing.T) {
|
|
314
|
+
application := NewApplication()
|
|
315
|
+
underlying := termwrightNewTestScreen(t)
|
|
316
|
+
replacement := termwrightNewTestScreen(t)
|
|
317
|
+
decorated := termwrightDecorator(application, underlying, nil)
|
|
318
|
+
application.screen = replacement
|
|
319
|
+
decorated.detach()
|
|
320
|
+
if application.screen != replacement {
|
|
321
|
+
t.Fatalf("cleanup clobbered later screen with %T", application.screen)
|
|
322
|
+
}
|
|
323
|
+
})
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
func TestTermwrightScreenFailsClosedBeforeRuntimeReplacement(t *testing.T) {
|
|
327
|
+
application := NewApplication()
|
|
328
|
+
underlying := termwrightNewTestScreen(t)
|
|
329
|
+
replacement := termwrightNewTestScreen(t)
|
|
330
|
+
finalized := 0
|
|
331
|
+
underlying.onFini = func() { finalized++ }
|
|
332
|
+
decorated := termwrightDecorator(application, underlying, nil)
|
|
333
|
+
failures := 0
|
|
334
|
+
decorated.fail = func(code, message string) {
|
|
335
|
+
if code != "adapter-guarantee-violation" || message == "" {
|
|
336
|
+
t.Fatalf("unexpected failure %q: %q", code, message)
|
|
337
|
+
}
|
|
338
|
+
failures++
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
application.SetScreen(replacement)
|
|
342
|
+
if failures != 1 || finalized != 1 || !decorated.failed.Load() || !decorated.detached.Load() {
|
|
343
|
+
t.Fatalf("replacement failures/finalized/failed/detached = %d/%d/%v/%v", failures, finalized, decorated.failed.Load(), decorated.detached.Load())
|
|
344
|
+
}
|
|
345
|
+
decorated.Fini()
|
|
346
|
+
if finalized != 1 {
|
|
347
|
+
t.Fatalf("underlying screen finalized %d times, want once", finalized)
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
func TestTermwrightScreenStopDoesNotReenterApplicationLock(t *testing.T) {
|
|
352
|
+
application := NewApplication()
|
|
353
|
+
underlying := termwrightNewTestScreen(t)
|
|
354
|
+
finalized := 0
|
|
355
|
+
underlying.onFini = func() { finalized++ }
|
|
356
|
+
decorated := termwrightDecorator(application, underlying, nil)
|
|
357
|
+
failures := 0
|
|
358
|
+
decorated.fail = func(string, string) { failures++ }
|
|
359
|
+
|
|
360
|
+
// This is the real upstream lifecycle: Stop holds Application.Lock while it
|
|
361
|
+
// invokes the decorated screen's Fini method.
|
|
362
|
+
application.Stop()
|
|
363
|
+
if finalized != 1 || failures != 0 || !decorated.failed.Load() || !decorated.detached.Load() {
|
|
364
|
+
t.Fatalf("stop finalized/failures/failed/detached = %d/%d/%v/%v", finalized, failures, decorated.failed.Load(), decorated.detached.Load())
|
|
365
|
+
}
|
|
366
|
+
decorated.detach()
|
|
367
|
+
if application.beforeDraw != nil || application.afterDraw != nil {
|
|
368
|
+
t.Fatal("cleanup after Stop left the probe lifecycle hooks installed")
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
func TestTermwrightScreenFiniUnderUnrelatedWriterFailsClosedWithoutBlocking(t *testing.T) {
|
|
373
|
+
application := NewApplication()
|
|
374
|
+
underlying := termwrightNewTestScreen(t)
|
|
375
|
+
commits := 0
|
|
376
|
+
decorated := termwrightDecorator(application, underlying, func(Primitive, tcell.Screen) {
|
|
377
|
+
commits++
|
|
378
|
+
})
|
|
379
|
+
failures := make(chan string, 1)
|
|
380
|
+
decorated.fail = func(code, message string) {
|
|
381
|
+
if code != "adapter-guarantee-violation" || message == "" {
|
|
382
|
+
t.Errorf("unexpected failure %q: %q", code, message)
|
|
383
|
+
}
|
|
384
|
+
failures <- message
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
application.Lock()
|
|
388
|
+
decorated.Fini()
|
|
389
|
+
application.Unlock()
|
|
390
|
+
<-failures
|
|
391
|
+
underlying.onShow = func() {}
|
|
392
|
+
decorated.Show()
|
|
393
|
+
if commits != 0 || !decorated.failed.Load() || !decorated.detached.Load() {
|
|
394
|
+
t.Fatalf("writer collision commits/failed/detached = %d/%v/%v", commits, decorated.failed.Load(), decorated.detached.Load())
|
|
395
|
+
}
|
|
396
|
+
decorated.detach()
|
|
397
|
+
application.Lock()
|
|
398
|
+
application.screen = nil
|
|
399
|
+
application.Unlock()
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
func TestTermwrightQueuePressureRequestsOneFreshAuthoritativeDraw(t *testing.T) {
|
|
403
|
+
application := NewApplication()
|
|
404
|
+
root := NewTextView().SetText("latest")
|
|
405
|
+
application.SetRoot(root, true)
|
|
406
|
+
underlying := termwrightNewTestScreen(t)
|
|
407
|
+
underlying.onShow = func() {}
|
|
408
|
+
commits := 0
|
|
409
|
+
termwrightDecorator(application, underlying, func(got Primitive, _ tcell.Screen) {
|
|
410
|
+
if got != root {
|
|
411
|
+
t.Fatalf("recovery committed %T, want latest root", got)
|
|
412
|
+
}
|
|
413
|
+
commits++
|
|
414
|
+
})
|
|
415
|
+
probe := &termwrightProbeState{
|
|
416
|
+
application: application,
|
|
417
|
+
recoveryStop: make(chan struct{}),
|
|
418
|
+
}
|
|
419
|
+
sink := &termwrightControlledPublicationSink{ready: make(chan struct{})}
|
|
420
|
+
|
|
421
|
+
probe.onPublishFailed(sink, protocol.ErrPublicationQueueFull)
|
|
422
|
+
probe.onPublishFailed(sink, protocol.ErrPublicationQueueFull)
|
|
423
|
+
if sink.failures.Load() != 0 || probe.dropped.Load() != 2 {
|
|
424
|
+
t.Fatalf("recoverable pressure failures/drops = %d/%d, want 0/2", sink.failures.Load(), probe.dropped.Load())
|
|
425
|
+
}
|
|
426
|
+
select {
|
|
427
|
+
case <-application.updates:
|
|
428
|
+
t.Fatal("redraw was queued before publication capacity returned")
|
|
429
|
+
default:
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
close(sink.ready)
|
|
433
|
+
update := <-application.updates
|
|
434
|
+
update.f()
|
|
435
|
+
if commits != 1 || probe.recoveryPending.Load() {
|
|
436
|
+
t.Fatalf("authoritative redraw commits/pending = %d/%v, want 1/false", commits, probe.recoveryPending.Load())
|
|
437
|
+
}
|
|
438
|
+
close(probe.recoveryStop)
|
|
439
|
+
probe.recoveryWorkers.Wait()
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
func TestTermwrightAdmissionContentionDefersOneFreshAuthoritativeDraw(t *testing.T) {
|
|
443
|
+
application := NewApplication()
|
|
444
|
+
root := NewTextView().SetText("after-contention")
|
|
445
|
+
application.SetRoot(root, true)
|
|
446
|
+
underlying := termwrightNewTestScreen(t)
|
|
447
|
+
underlying.onShow = func() {}
|
|
448
|
+
commits := 0
|
|
449
|
+
termwrightDecorator(application, underlying, func(got Primitive, _ tcell.Screen) {
|
|
450
|
+
if got != root {
|
|
451
|
+
t.Fatalf("deferred draw committed %T, want latest root", got)
|
|
452
|
+
}
|
|
453
|
+
commits++
|
|
454
|
+
})
|
|
455
|
+
probe := &termwrightProbeState{
|
|
456
|
+
application: application,
|
|
457
|
+
recoveryStop: make(chan struct{}),
|
|
458
|
+
}
|
|
459
|
+
sink := &termwrightControlledPublicationSink{ready: make(chan struct{})}
|
|
460
|
+
|
|
461
|
+
probe.onPublishFailed(sink, protocol.ErrPublicationQueueBusy)
|
|
462
|
+
probe.onPublishFailed(sink, protocol.ErrPublicationQueueBusy)
|
|
463
|
+
update := <-application.updates
|
|
464
|
+
update.f()
|
|
465
|
+
if sink.failures.Load() != 0 || probe.dropped.Load() != 2 || commits != 1 || probe.recoveryPending.Load() {
|
|
466
|
+
t.Fatalf(
|
|
467
|
+
"contention failures/drops/commits/pending = %d/%d/%d/%v, want 0/2/1/false",
|
|
468
|
+
sink.failures.Load(), probe.dropped.Load(), commits, probe.recoveryPending.Load(),
|
|
469
|
+
)
|
|
470
|
+
}
|
|
471
|
+
close(probe.recoveryStop)
|
|
472
|
+
probe.recoveryWorkers.Wait()
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
func TestTermwrightNonPressurePublicationRefusalStillFailsClosed(t *testing.T) {
|
|
476
|
+
probe := &termwrightProbeState{recoveryStop: make(chan struct{})}
|
|
477
|
+
sink := &termwrightControlledPublicationSink{ready: make(chan struct{})}
|
|
478
|
+
probe.onPublishFailed(sink, errors.New("invalid snapshot"))
|
|
479
|
+
if sink.failures.Load() != 1 || probe.dropped.Load() != 1 {
|
|
480
|
+
t.Fatalf("hard refusal failures/drops = %d/%d, want 1/1", sink.failures.Load(), probe.dropped.Load())
|
|
481
|
+
}
|
|
482
|
+
close(probe.recoveryStop)
|
|
483
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -39,49 +39,33 @@ declare function roleFor(frameworkType: string): SemanticRole;
|
|
|
39
39
|
*/
|
|
40
40
|
declare function recognize(frame: ProbeFrame, options: RecognizeOptions): SemanticSnapshot;
|
|
41
41
|
|
|
42
|
-
/**
|
|
43
|
-
* The one call a launcher makes.
|
|
44
|
-
*
|
|
45
|
-
* Everything else in this package is a step: read the workspace, materialise a
|
|
46
|
-
* copy, patch it, key the cache, write the file. A user should not assemble
|
|
47
|
-
* those, and neither should a test — the assembly order is where the mistakes
|
|
48
|
-
* live, and there is exactly one correct one.
|
|
49
|
-
*/
|
|
50
|
-
/** Module path of the framework this probe instruments. */
|
|
42
|
+
/** Capability-driven, add-only compiler injection for tview. */
|
|
51
43
|
declare const FRAMEWORK = "github.com/rivo/tview";
|
|
52
|
-
/** Module path of the protocol client the injected probe imports. */
|
|
53
44
|
declare const CLIENT_MODULE = "github.com/gorce-ai/termwright/clients/go";
|
|
54
|
-
|
|
55
|
-
declare const PROBE_VERSION = "0.1.0";
|
|
45
|
+
declare const PROBE_VERSION = "0.3.1";
|
|
56
46
|
interface PrepareOptions {
|
|
57
|
-
/** Directory of the Go module to build. */
|
|
58
47
|
readonly moduleDir: string;
|
|
59
|
-
/**
|
|
48
|
+
/** Advisory expectation; runtime capability compilation is authoritative. */
|
|
60
49
|
readonly frameworkVersion?: string;
|
|
61
|
-
|
|
62
|
-
readonly clientDir?: string;
|
|
63
|
-
/** Where the generated workspace is written. Defaults to inside the copy's cache entry. */
|
|
64
|
-
readonly workspaceFile?: string;
|
|
65
|
-
/** Environment the build will run with; checked, never mutated. */
|
|
50
|
+
readonly outputDir?: string;
|
|
66
51
|
readonly env?: NodeJS.ProcessEnv;
|
|
67
52
|
}
|
|
68
53
|
interface PreparedBuild {
|
|
69
|
-
/**
|
|
70
|
-
readonly
|
|
71
|
-
/**
|
|
72
|
-
readonly
|
|
73
|
-
/** Environment to build with: the caller's, plus GOWORK. */
|
|
54
|
+
/** Canonical module directory that must be used as the build cwd. */
|
|
55
|
+
readonly moduleDir: string;
|
|
56
|
+
/** Insert after `go build` or `go test`. */
|
|
57
|
+
readonly goArgs: readonly [string, string];
|
|
74
58
|
readonly env: NodeJS.ProcessEnv;
|
|
75
|
-
|
|
76
|
-
readonly
|
|
59
|
+
readonly wrapperFile: string;
|
|
60
|
+
readonly configDigest: string;
|
|
61
|
+
readonly frameworkVersion: string;
|
|
62
|
+
readonly sourceDigests: readonly string[];
|
|
77
63
|
}
|
|
78
64
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
* package testable and keeps the decision about *how* to run the user's build
|
|
84
|
-
* where it belongs.
|
|
65
|
+
* Prepares one official Go `-toolexec` wrapper. It adds compilation units to
|
|
66
|
+
* package namespaces selected by TOOLEXEC_IMPORTPATH and therefore works for
|
|
67
|
+
* module-cache, workspace, replacement, and vendored dependency layouts.
|
|
68
|
+
* No upstream path is copied, patched, or hashed.
|
|
85
69
|
*/
|
|
86
70
|
declare function prepareInstrumentedBuild(options: PrepareOptions): Promise<PreparedBuild>;
|
|
87
71
|
|