@@ -74,8 +74,8 @@ func TestWriteSingleFrameCompressed(t *testing.T) {
7474 var (
7575 flateThreshold = 64
7676
77- largeMsg = []byte (strings .Repeat ("hello world " , 100 )) // ~1200 bytes, above threshold
78- smallMsg = []byte ("small message" ) // 13 bytes, below threshold
77+ largeMsg = []byte (strings .Repeat ("hello world " , 100 ))
78+ smallMsg = []byte ("small message" )
7979 )
8080
8181 testCases := []struct {
@@ -182,15 +182,16 @@ func TestWriteThenWriterContextTakeover(t *testing.T) {
182182 }
183183 }()
184184
185- // First message: Write() redirects flateWriter to temp buffer
186- assert .Success (t , client .Write (ctx , MessageText , msg1 ))
185+ // 2. `Write` API
186+ err := client .Write (ctx , MessageText , msg1 )
187+ assert .Success (t , err )
187188
188189 r := <- readCh
189190 assert .Success (t , r .err )
190191 assert .Equal (t , "msg1 type" , MessageText , r .typ )
191192 assert .Equal (t , "msg1 content" , string (msg1 ), string (r .p ))
192193
193- // Second message: Writer() streaming API
194+ // 2. ` Writer` API
194195 w , err := client .Writer (ctx , MessageBinary )
195196 assert .Success (t , err )
196197 _ , err = w .Write (msg2 )
@@ -202,3 +203,92 @@ func TestWriteThenWriterContextTakeover(t *testing.T) {
202203 assert .Equal (t , "msg2 type" , MessageBinary , r .typ )
203204 assert .Equal (t , "msg2 content" , string (msg2 ), string (r .p ))
204205}
206+
207+ // TestCompressionDictionaryPreserved verifies that context takeover mode
208+ // preserves the compression dictionary across Conn.Write calls, resulting
209+ // in better compression for consecutive similar messages.
210+ func TestCompressionDictionaryPreserved (t * testing.T ) {
211+ t .Parallel ()
212+
213+ msg := []byte (strings .Repeat (`{"type":"event","data":"value"}` , 50 ))
214+
215+ // Test with context takeover
216+ clientConn1 , serverConn1 := net .Pipe ()
217+ defer clientConn1 .Close ()
218+ defer serverConn1 .Close ()
219+
220+ withTakeover := newConn (connConfig {
221+ rwc : clientConn1 ,
222+ client : true ,
223+ copts : CompressionContextTakeover .opts (),
224+ flateThreshold : 64 ,
225+ br : bufio .NewReader (clientConn1 ),
226+ bw : bufio .NewWriterSize (clientConn1 , 4096 ),
227+ })
228+
229+ // Test without context takeover
230+ clientConn2 , serverConn2 := net .Pipe ()
231+ defer clientConn2 .Close ()
232+ defer serverConn2 .Close ()
233+
234+ withoutTakeover := newConn (connConfig {
235+ rwc : clientConn2 ,
236+ client : true ,
237+ copts : CompressionNoContextTakeover .opts (),
238+ flateThreshold : 64 ,
239+ br : bufio .NewReader (clientConn2 ),
240+ bw : bufio .NewWriterSize (clientConn2 , 4096 ),
241+ })
242+
243+ ctx , cancel := context .WithTimeout (context .Background (), time .Second )
244+ defer cancel ()
245+
246+ // Capture compressed sizes for both modes
247+ var withTakeoverSizes , withoutTakeoverSizes []int64
248+
249+ reader1 := bufio .NewReader (serverConn1 )
250+ reader2 := bufio .NewReader (serverConn2 )
251+ readBuf := make ([]byte , 8 )
252+
253+ // Send 3 identical messages each
254+ for range 3 {
255+ // With context takeover
256+ writeDone1 := make (chan error , 1 )
257+ go func () {
258+ writeDone1 <- withTakeover .Write (ctx , MessageText , msg )
259+ }()
260+
261+ h1 , err := readFrameHeader (reader1 , readBuf )
262+ assert .Success (t , err )
263+
264+ _ , err = io .CopyN (io .Discard , reader1 , h1 .payloadLength )
265+ assert .Success (t , err )
266+
267+ withTakeoverSizes = append (withTakeoverSizes , h1 .payloadLength )
268+ assert .Success (t , <- writeDone1 )
269+
270+ // Without context takeover
271+ writeDone2 := make (chan error , 1 )
272+ go func () {
273+ writeDone2 <- withoutTakeover .Write (ctx , MessageText , msg )
274+ }()
275+
276+ h2 , err := readFrameHeader (reader2 , readBuf )
277+ assert .Success (t , err )
278+
279+ _ , err = io .CopyN (io .Discard , reader2 , h2 .payloadLength )
280+ assert .Success (t , err )
281+
282+ withoutTakeoverSizes = append (withoutTakeoverSizes , h2 .payloadLength )
283+ assert .Success (t , <- writeDone2 )
284+ }
285+
286+ // With context takeover, the 2nd and 3rd messages should be smaller
287+ // than without context takeover (dictionary helps compress repeated patterns).
288+ // The first message will be similar size for both modes since there's no
289+ // prior dictionary. But subsequent messages benefit from context takeover.
290+ if withTakeoverSizes [2 ] >= withoutTakeoverSizes [2 ] {
291+ t .Errorf ("context takeover should compress better: with=%d, without=%d" ,
292+ withTakeoverSizes [2 ], withoutTakeoverSizes [2 ])
293+ }
294+ }
0 commit comments