@@ -3,17 +3,23 @@ package relay
33import (
44 "bytes"
55 "compress/gzip"
6+ "fmt"
67 "io"
78 "net/http"
89 "net/http/httptest"
10+ "runtime"
11+ "runtime/debug"
12+ "sync"
913 "testing"
14+ "time"
1015
1116 c "github.com/launchdarkly/ld-relay/v8/config"
1217
1318 "github.com/launchdarkly/go-configtypes"
1419 "github.com/launchdarkly/go-sdk-common/v3/ldlog"
1520 "github.com/launchdarkly/go-test-helpers/v3/httphelpers"
1621
22+ "github.com/klauspost/compress/gzhttp"
1723 "github.com/stretchr/testify/assert"
1824 "github.com/stretchr/testify/require"
1925)
@@ -185,9 +191,10 @@ func TestCompressionIsAppliedWhenEnabled(t *testing.T) {
185191 }
186192
187193 withStartedRelay (t , configWithCompression , func (p relayTestParams ) {
188- // Create a request to the status endpoint
189- req , _ := http .NewRequest ("GET" , "/status " , nil )
194+ // Create a request to the flags endpoint which returns more data
195+ req , _ := http .NewRequest ("GET" , "/sdk/flags " , nil )
190196 req .Header .Set ("Accept-Encoding" , "gzip" )
197+ req .Header .Set ("Authorization" , "test-key" )
191198
192199 w := httptest .NewRecorder ()
193200 p .relay .ServeHTTP (w , req )
@@ -208,6 +215,9 @@ func TestCompressionIsAppliedWhenEnabled(t *testing.T) {
208215 decompressed , err := io .ReadAll (reader )
209216 assert .NoError (t , err , "Should be able to read decompressed content" )
210217 assert .Greater (t , len (decompressed ), 0 , "Decompressed content should not be empty" )
218+
219+ // Verify the decompressed content is substantial enough to test compression
220+ assert .Greater (t , len (decompressed ), gzhttp .DefaultMinSize , fmt .Sprintf ("Decompressed content should be larger than %d bytes to properly test compression" , gzhttp .DefaultMinSize ))
211221 })
212222}
213223
@@ -225,9 +235,10 @@ func TestCompressionIsNotAppliedWhenDisabled(t *testing.T) {
225235 }
226236
227237 withStartedRelay (t , configWithoutCompression , func (p relayTestParams ) {
228- // Create a request to the status endpoint
229- req , _ := http .NewRequest ("GET" , "/status " , nil )
238+ // Create a request to the flags endpoint which returns more data
239+ req , _ := http .NewRequest ("GET" , "/sdk/flags " , nil )
230240 req .Header .Set ("Accept-Encoding" , "gzip" )
241+ req .Header .Set ("Authorization" , "test-key" )
231242
232243 w := httptest .NewRecorder ()
233244 p .relay .ServeHTTP (w , req )
@@ -240,8 +251,132 @@ func TestCompressionIsNotAppliedWhenDisabled(t *testing.T) {
240251 body := w .Body .Bytes ()
241252 assert .Greater (t , len (body ), 0 , "Response body should not be empty" )
242253
254+ // Verify the uncompressed content is substantial enough
255+ assert .Greater (t , len (body ), gzhttp .DefaultMinSize , fmt .Sprintf ("Uncompressed content should be larger than %d bytes" , gzhttp .DefaultMinSize ))
256+
257+ t .Logf ("Body size: %d bytes" , len (body ))
243258 // Try to decompress the body - this should fail since it's not compressed
244259 _ , err := gzip .NewReader (io .NopCloser (bytes .NewReader (body )))
245260 assert .Error (t , err , "Response should not be gzip content when compression is disabled" )
246261 })
247262}
263+
264+ func TestLoadCompression (t * testing.T ) {
265+ // Set minimal memory limit for the test
266+ originalLimit := debug .SetMemoryLimit (10 * 1024 * 1024 ) // 10MB limit
267+ defer debug .SetMemoryLimit (originalLimit )
268+
269+ // Force garbage collection before starting
270+ runtime .GC ()
271+
272+ // Test with compression enabled
273+ configWithCompression := c.Config {
274+ HTTP : c.HTTPConfig {
275+ EnableCompression : true ,
276+ },
277+ Environment : map [string ]* c.EnvConfig {
278+ "test" : {
279+ SDKKey : "test-key" ,
280+ },
281+ },
282+ }
283+
284+ withStartedRelay (t , configWithCompression , func (p relayTestParams ) {
285+ var wg sync.WaitGroup
286+ var mu sync.Mutex
287+ var requestCount int
288+ var lastError error
289+ var failed bool
290+ var maxAlloc uint64
291+
292+ // Start with a reasonable number of concurrent requests
293+ concurrency := 100
294+ maxRequests := 1000
295+
296+ for i := 0 ; i < concurrency ; i ++ {
297+ wg .Add (1 )
298+ go func () {
299+ defer wg .Done ()
300+
301+ for {
302+ mu .Lock ()
303+ if failed || requestCount >= maxRequests {
304+ mu .Unlock ()
305+ return
306+ }
307+ currentCount := requestCount
308+ requestCount ++
309+ mu .Unlock ()
310+
311+ // Create a request to the flags endpoint
312+ req , err := http .NewRequest ("GET" , "/sdk/flags" , nil )
313+ if err != nil {
314+ mu .Lock ()
315+ lastError = err
316+ failed = true
317+ mu .Unlock ()
318+ return
319+ }
320+
321+ req .Header .Set ("Accept-Encoding" , "gzip" )
322+ req .Header .Set ("Authorization" , "test-key" )
323+
324+ w := httptest .NewRecorder ()
325+ p .relay .ServeHTTP (w , req )
326+
327+ // Check if the request succeeded
328+ if w .Result ().StatusCode != http .StatusOK {
329+ mu .Lock ()
330+ lastError = fmt .Errorf ("request failed with status: %d" , w .Result ().StatusCode )
331+ failed = true
332+ mu .Unlock ()
333+ return
334+ }
335+
336+ // Verify compression is working
337+ if w .Header ().Get ("Content-Encoding" ) != "gzip" {
338+ mu .Lock ()
339+ lastError = fmt .Errorf ("compression not applied at request %d" , currentCount )
340+ failed = true
341+ mu .Unlock ()
342+ return
343+ }
344+
345+ // Check memory usage
346+ var m runtime.MemStats
347+ runtime .ReadMemStats (& m )
348+
349+ // Track maximum memory usage
350+ mu .Lock ()
351+ if m .Alloc > maxAlloc {
352+ maxAlloc = m .Alloc
353+ }
354+ mu .Unlock ()
355+
356+ // Small delay to prevent overwhelming the system
357+ time .Sleep (1 * time .Millisecond )
358+ }
359+ }()
360+ }
361+
362+ wg .Wait ()
363+
364+ // Report results
365+ t .Logf ("Load test completed: %d requests processed" , requestCount )
366+
367+ if failed {
368+ t .Fatalf ("Test failed: %v" , lastError )
369+ }
370+
371+ t .Logf ("Test completed successfully with %d requests" , requestCount )
372+ assert .Greater (t , requestCount , 0 , "Should have processed requests successfully" )
373+
374+ // Final memory stats
375+ var m runtime.MemStats
376+ runtime .ReadMemStats (& m )
377+ t .Logf ("Final memory usage: Alloc=%d MB, TotalAlloc=%d MB, NumGC=%d" ,
378+ m .Alloc / 1024 / 1024 , m .TotalAlloc / 1024 / 1024 , m .NumGC )
379+ t .Logf ("Peak memory usage: MaxAlloc=%d MB" ,
380+ maxAlloc / 1024 / 1024 )
381+ })
382+ }
0 commit comments