返回 DeepSeek-Reasonix
stream_test.go
根目录 / internal / persistentshell / stream_test.go
1 package persistentshell
2
3 import (
4 "strings"
5 "testing"
6 )
7
8 func TestSanitizerStripsTerminalControls(t *testing.T) {
9 var s sanitizer
10 got := s.push([]byte("\x1b[31mred\x1b[0m done\x1b]0;title\x07!"))
11 if got != "red done!" {
12 t.Fatalf("got %q", got)
13 }
14 }
15
16 // An escape sequence split across two PTY reads must not leak its bytes.
17 func TestSanitizerCarriesSplitEscape(t *testing.T) {
18 var s sanitizer
19 first := s.push([]byte("a\x1b[3"))
20 second := s.push([]byte("1mb"))
21 if first+second != "ab" {
22 t.Fatalf("got %q+%q", first, second)
23 }
24 }
25
26 // A carriage return at a read boundary cannot be classified until the next
27 // byte arrives; classifying it early produced a spurious blank line.
28 func TestSanitizerCarriesTrailingCarriageReturn(t *testing.T) {
29 var s sanitizer
30 first := s.push([]byte("a\r"))
31 second := s.push([]byte("\nb"))
32 if first+second != "a\nb" {
33 t.Fatalf("got %q+%q", first, second)
34 }
35
36 var lone sanitizer
37 if got := lone.push([]byte("a\r")) + lone.push([]byte("b")); got != "a\nb" {
38 t.Fatalf("lone CR: got %q", got)
39 }
40 }
41
42 func TestCaptureExtractsBodyAndStatus(t *testing.T) {
43 c := newCapture("S", "E:", nil)
44 c.push("noise\nS\n")
45 c.push("hello\n")
46 c.push("E:0\n")
47 if !c.done || c.exitCode != 0 {
48 t.Fatalf("done=%v code=%d", c.done, c.exitCode)
49 }
50 if c.body() != "hello\n" {
51 t.Fatalf("body=%q", c.body())
52 }
53 }
54
55 // The end marker may arrive split across reads, and its status digits may
56 // arrive after the marker itself.
57 func TestCaptureHandlesSplitEndMarker(t *testing.T) {
58 c := newCapture("S", "E:", nil)
59 c.push("S\nbody")
60 c.push("E")
61 if c.done {
62 t.Fatal("marker prefix alone must not complete")
63 }
64 c.push(":")
65 if c.done {
66 t.Fatal("marker without status must not complete")
67 }
68 c.push("12")
69 if c.done {
70 t.Fatal("status without terminator must not complete")
71 }
72 c.push("\n")
73 if !c.done || c.exitCode != 12 {
74 t.Fatalf("done=%v code=%d", c.done, c.exitCode)
75 }
76 if c.body() != "body" {
77 t.Fatalf("body=%q", c.body())
78 }
79 }
80
81 // Output with no trailing newline leaves the marker mid-line.
82 func TestCaptureBodyWithoutTrailingNewline(t *testing.T) {
83 c := newCapture("S", "E:", nil)
84 c.push("S\nhiE:0\n")
85 if !c.done || c.body() != "hi" {
86 t.Fatalf("done=%v body=%q", c.done, c.body())
87 }
88 }
89
90 // A command that never reports a status still owes the model its output.
91 func TestCapturePartialReleasesWithheldText(t *testing.T) {
92 c := newCapture("S", "E:", nil)
93 c.push("S\npartial output")
94 if c.body() == "partial output" {
95 t.Fatal("tail must stay withheld until the command settles")
96 }
97 if got := c.partial(); got != "partial output" {
98 t.Fatalf("partial=%q", got)
99 }
100 }
101
102 // Large output must not be rescanned per read: the withheld window stays at
103 // marker width no matter how much text passed through.
104 func TestCaptureHoldStaysBounded(t *testing.T) {
105 c := newCapture("S", "E:", nil)
106 c.push("S\n")
107 for range 100 {
108 c.push(strings.Repeat("x", 4096))
109 }
110 if len(c.hold) > markerOverlap {
111 t.Fatalf("hold grew to %d bytes", len(c.hold))
112 }
113 if len(c.body()) < 100*4096-markerOverlap {
114 t.Fatalf("body lost data: %d bytes", len(c.body()))
115 }
116 }
117
118 // A shell tracing its own input (set -x) prints the end marker followed by
119 // wrapper source instead of digits. That trailer is output, not a completion,
120 // and must not park the reader until the deadline.
121 func TestCaptureSkipsMarkerWithNonStatusTrailer(t *testing.T) {
122 c := newCapture("S", "E:", nil)
123 c.push("S\n+ printf '%s%s\\n' E:' \"$__rx_status\"\n")
124 if c.done {
125 t.Fatal("a traced marker must not complete the command")
126 }
127 c.push("real output\nE:3\n")
128 if !c.done || c.exitCode != 3 {
129 t.Fatalf("done=%v code=%d", c.done, c.exitCode)
130 }
131 if !strings.Contains(c.body(), "real output") {
132 t.Fatalf("body=%q", c.body())
133 }
134 }
135
136 // A CRLF host puts \r before the status terminator.
137 func TestCaptureAcceptsCarriageReturnStatus(t *testing.T) {
138 c := newCapture("S", "E:", nil)
139 c.push("S\nout\nE:5\r\n")
140 if !c.done || c.exitCode != 5 {
141 t.Fatalf("done=%v code=%d", c.done, c.exitCode)
142 }
143 }
144
144 lines GO