@@ -4,6 +4,7 @@ package integTest
44
55import (
66 "context"
7+ "fmt"
78 "strings"
89 "sync"
910 "time"
@@ -14,6 +15,147 @@ import (
1415 "github.com/valkey-io/valkey-glide/go/v2/internal/interfaces"
1516)
1617
18+ func newDedicatedValkey (suite * GlideTestSuite , clusterMode bool ) (string , error ) {
19+ // Build command arguments
20+ args := []string {}
21+ args = append (args , "start" )
22+ if clusterMode {
23+ args = append (args , "--cluster-mode" )
24+ }
25+
26+ // shardCount := 1
27+ // if clusterMode {
28+ // shardCount = 3
29+ // }
30+ // args = append(args, fmt.Sprintf("-n %d", shardCount))
31+ args = append (args , fmt .Sprintf ("-r %d" , 1 ))
32+
33+ // Execute cluster manager script
34+ output := runClusterManager (suite , args , false )
35+
36+ return output , nil
37+ }
38+
39+ func stopDedicatedValkey (suite * GlideTestSuite , clusterFolder string ) {
40+ args := []string {}
41+ args = append (args , "stop" , "--cluster-folder" , clusterFolder )
42+
43+ runClusterManager (suite , args , false )
44+ }
45+
46+ func createDedicatedClient (
47+ addresses []config.NodeAddress ,
48+ clusterMode bool ,
49+ lazyConnect bool ,
50+ ) (interfaces.BaseClientCommands , error ) {
51+ if clusterMode {
52+ cfg := config .NewClusterClientConfiguration ()
53+ for _ , addr := range addresses {
54+ cfg .WithAddress (& addr )
55+ }
56+
57+ cfg .WithRequestTimeout (3 * time .Second )
58+ advCfg := config .NewAdvancedClusterClientConfiguration ()
59+ advCfg .WithConnectionTimeout (3 * time .Second )
60+ cfg .WithAdvancedConfiguration (advCfg )
61+ cfg .WithLazyConnect (lazyConnect )
62+
63+ return glide .NewClusterClient (cfg )
64+ }
65+
66+ cfg := config .NewClientConfiguration ()
67+ for _ , addr := range addresses {
68+ cfg .WithAddress (& addr )
69+ }
70+
71+ cfg .WithRequestTimeout (3 * time .Second )
72+ advCfg := config .NewAdvancedClientConfiguration ()
73+ advCfg .WithConnectionTimeout (3 * time .Second )
74+ cfg .WithAdvancedConfiguration (advCfg )
75+ cfg .WithLazyConnect (lazyConnect )
76+
77+ return glide .NewClient (cfg )
78+ }
79+
80+ // getClientListOutputCount parses CLIENT LIST output and returns the number of clients
81+ func getClientListOutputCount (output interface {}) int {
82+ if output == nil {
83+ return 0
84+ }
85+
86+ var text string
87+ switch v := output .(type ) {
88+ case []byte :
89+ text = string (v )
90+ case string :
91+ text = v
92+ default :
93+ return 0
94+ }
95+
96+ if text = strings .TrimSpace (text ); text == "" {
97+ return 0
98+ }
99+
100+ return len (strings .Split (text , "\n " ))
101+ }
102+
103+ // getClientCount returns the number of connected clients
104+ func getClientCount (ctx context.Context , client interfaces.BaseClientCommands ) (int , error ) {
105+ if clusterClient , ok := client .(interfaces.GlideClusterClientCommands ); ok {
106+ // For cluster client, execute CLIENT LIST on all nodes
107+ result , err := clusterClient .CustomCommandWithRoute (ctx , []string {"CLIENT" , "LIST" }, config .AllNodes )
108+ if err != nil {
109+ return 0 , err
110+ }
111+
112+ // Result will be a map with node addresses as keys and CLIENT LIST output as values
113+ totalCount := 0
114+ for _ , nodeOutput := range result .MultiValue () {
115+ totalCount += getClientListOutputCount (nodeOutput )
116+ }
117+ return totalCount , nil
118+ }
119+
120+ // For standalone client, execute CLIENT LIST directly
121+ glideClient := client .(interfaces.GlideClientCommands )
122+ result , err := glideClient .CustomCommand (ctx , []string {"CLIENT" , "LIST" })
123+ if err != nil {
124+ return 0 , err
125+ }
126+ return getClientListOutputCount (result ), nil
127+ }
128+
129+ // getExpectedNewConnections returns the expected number of new connections when a lazy client is initialized
130+ func getExpectedNewConnections (ctx context.Context , client interfaces.BaseClientCommands ) (int , error ) {
131+ if clusterClient , ok := client .(interfaces.GlideClusterClientCommands ); ok {
132+ // For cluster, get node count and multiply by 2 (2 connections per node)
133+ result , err := clusterClient .CustomCommand (ctx , []string {"CLUSTER" , "NODES" })
134+ if err != nil {
135+ return 0 , err
136+ }
137+
138+ var nodesInfo string
139+ switch v := result .SingleValue ().(type ) {
140+ case []byte :
141+ nodesInfo = string (v )
142+ case string :
143+ nodesInfo = v
144+ default :
145+ nodesInfo = ""
146+ }
147+
148+ if nodesInfo = strings .TrimSpace (nodesInfo ); nodesInfo == "" {
149+ return 0 , nil
150+ }
151+
152+ return len (strings .Split (nodesInfo , "\n " )) * 2 , nil
153+ }
154+
155+ // For standalone, always expect 1 new connection
156+ return 1 , nil
157+ }
158+
17159func (suite * GlideTestSuite ) TestStandaloneConnect () {
18160 config := config .NewClientConfiguration ().
19161 WithAddress (& suite .standaloneHosts [0 ])
@@ -156,3 +298,60 @@ func (suite *GlideTestSuite) TestConnectionTimeout() {
156298 }
157299 })
158300}
301+
302+ func (suite * GlideTestSuite ) TestLazyConnectionEstablishesOnFirstCommand () {
303+ // Run test for both standalone and cluster modes
304+ suite .runWithTimeoutClients (func (client interfaces.BaseClientCommands ) {
305+ ctx := context .Background ()
306+ _ , isCluster := client .(interfaces.GlideClusterClientCommands )
307+
308+ // Create a monitoring client (eagerly connected)
309+ output , err := newDedicatedValkey (suite , isCluster )
310+ suite .NoError (err )
311+ clusterFolder := extractClusterFolder (suite , output )
312+ addresses := extractAddresses (suite , output )
313+ defer stopDedicatedValkey (suite , clusterFolder )
314+ monitoringClient , err := createDedicatedClient (addresses , isCluster , false )
315+ suite .NoError (err )
316+ defer monitoringClient .Close ()
317+
318+ // Get initial client count
319+ clientsBeforeLazyInit , err := getClientCount (ctx , monitoringClient )
320+ suite .NoError (err )
321+
322+ // Create the "lazy" client
323+ lazyClient , err := createDedicatedClient (addresses , isCluster , true )
324+ suite .NoError (err )
325+ defer lazyClient .Close ()
326+
327+ // Check count (should not change)
328+ clientsAfterLazyInit , err := getClientCount (ctx , monitoringClient )
329+ suite .NoError (err )
330+ suite .Equal (clientsBeforeLazyInit , clientsAfterLazyInit ,
331+ "Lazy client should not connect before the first command" )
332+
333+ // Send the first command using the lazy client
334+ var result interface {}
335+ if isCluster {
336+ clusterClient := lazyClient .(interfaces.GlideClusterClientCommands )
337+ result , err = clusterClient .Ping (ctx )
338+ } else {
339+ glideClient := lazyClient .(interfaces.GlideClientCommands )
340+ result , err = glideClient .Ping (ctx )
341+ }
342+ suite .NoError (err )
343+
344+ // Assert PING success for both modes
345+ suite .Equal ("PONG" , result )
346+
347+ // Check client count after the first command
348+ clientsAfterFirstCommand , err := getClientCount (ctx , monitoringClient )
349+ suite .NoError (err )
350+
351+ expectedNewConnections , err := getExpectedNewConnections (ctx , monitoringClient )
352+ suite .NoError (err )
353+
354+ suite .Equal (clientsBeforeLazyInit + expectedNewConnections , clientsAfterFirstCommand ,
355+ "Lazy client should establish expected number of new connections after the first command" )
356+ })
357+ }
0 commit comments