@@ -21,6 +21,12 @@ type Suite struct {
21
21
Category string // Category of the test suite [Optional]
22
22
Description string // Description of the test suite (if empty, suite won't appear in documentation) [Optional]
23
23
Tests []AnyTest
24
+
25
+ // SharedClients maps client IDs to client instances that are shared across tests
26
+ SharedClients map [string ]* Client
27
+
28
+ // Internal tracking
29
+ sharedClientOpts map [string ][]StartOption // Stores options for starting shared clients
24
30
}
25
31
26
32
func (s * Suite ) request () * simapi.TestRequest {
@@ -39,6 +45,30 @@ func (s *Suite) Add(test AnyTest) *Suite {
39
45
return s
40
46
}
41
47
48
+ // AddSharedClient registers a client to be shared across all tests in the suite.
49
+ // The client will be started when the suite begins and terminated when the suite ends.
50
+ // This is useful for maintaining state across tests for incremental testing or
51
+ // avoiding client initialization for every test.
52
+ func (s * Suite ) AddSharedClient (clientID string , clientType string , options ... StartOption ) * Suite {
53
+ if s .SharedClients == nil {
54
+ s .SharedClients = make (map [string ]* Client )
55
+ }
56
+ if s .sharedClientOpts == nil {
57
+ s .sharedClientOpts = make (map [string ][]StartOption )
58
+ }
59
+
60
+ // Store options for later use when the suite is started
61
+ s .sharedClientOpts [clientID ] = append ([]StartOption {}, options ... )
62
+
63
+ // Create a placeholder client that will be initialized when the suite runs
64
+ s .SharedClients [clientID ] = & Client {
65
+ Type : clientType ,
66
+ IsShared : true ,
67
+ }
68
+
69
+ return s
70
+ }
71
+
42
72
// AnyTest is a TestSpec or ClientTestSpec.
43
73
type AnyTest interface {
44
74
runTest (* Simulation , SuiteID , * Suite ) error
@@ -77,11 +107,46 @@ func RunSuite(host *Simulation, suite Suite) error {
77
107
}
78
108
defer host .EndSuite (suiteID )
79
109
110
+ // Start shared clients for the suite
111
+ if len (suite .SharedClients ) > 0 {
112
+ fmt .Printf ("Starting %d shared clients for suite %s...\n " , len (suite .SharedClients ), suite .Name )
113
+
114
+ // Initialize any shared clients defined for this suite
115
+ for clientID , client := range suite .SharedClients {
116
+ // Retrieve stored options for this client
117
+ options := suite .sharedClientOpts [clientID ]
118
+
119
+ // Start the shared client
120
+ containerID , ip , err := host .StartSharedClient (suiteID , client .Type , options ... )
121
+ if err != nil {
122
+ fmt .Fprintf (os .Stderr , "Error starting shared client %s: %v\n " , clientID , err )
123
+ return err
124
+ }
125
+
126
+ // Update the client object with actual container information
127
+ client .Container = containerID
128
+ client .IP = ip
129
+ client .SuiteID = suiteID
130
+ client .IsShared = true
131
+
132
+ fmt .Printf ("Started shared client %s (container: %s)\n " , clientID , containerID )
133
+ }
134
+ }
135
+
136
+ // Run all tests in the suite
80
137
for _ , test := range suite .Tests {
81
138
if err := test .runTest (host , suiteID , & suite ); err != nil {
82
139
return err
83
140
}
84
141
}
142
+
143
+ // Clean up any shared clients at the end of the suite
144
+ // They are automatically stopped when the suite ends via defer host.EndSuite(suiteID) above
145
+ // But we should output a message for clarity
146
+ if len (suite .SharedClients ) > 0 {
147
+ fmt .Printf ("Cleaning up %d shared clients for suite %s...\n " , len (suite .SharedClients ), suite .Name )
148
+ }
149
+
85
150
return nil
86
151
}
87
152
@@ -164,6 +229,11 @@ type Client struct {
164
229
rpc * rpc.Client
165
230
enginerpc * rpc.Client
166
231
test * T
232
+
233
+ // Fields for shared client support
234
+ IsShared bool // Whether this client is shared across tests
235
+ LogPosition int64 // Current position in the log file (for shared clients)
236
+ SuiteID SuiteID // The suite this client belongs to (for shared clients)
167
237
}
168
238
169
239
// EnodeURL returns the default peer-to-peer endpoint of the client.
@@ -227,6 +297,9 @@ type T struct {
227
297
suite * Suite
228
298
mu sync.Mutex
229
299
result TestResult
300
+
301
+ // Fields for tracking client logs
302
+ clientLogOffsets map [string ]* LogOffset // Tracks log offsets for clients used in this test
230
303
}
231
304
232
305
// StartClient starts a client instance. If the client cannot by started, the test fails immediately.
@@ -235,7 +308,59 @@ func (t *T) StartClient(clientType string, option ...StartOption) *Client {
235
308
if err != nil {
236
309
t .Fatalf ("can't launch node (type %s): %v" , clientType , err )
237
310
}
238
- return & Client {Type : clientType , Container : container , IP : ip , test : t }
311
+
312
+ // Initialize log tracking for this client
313
+ if t .clientLogOffsets == nil {
314
+ t .clientLogOffsets = make (map [string ]* LogOffset )
315
+ }
316
+
317
+ return & Client {
318
+ Type : clientType ,
319
+ Container : container ,
320
+ IP : ip ,
321
+ test : t ,
322
+ IsShared : false ,
323
+ }
324
+ }
325
+
326
+ // GetSharedClient retrieves a shared client by ID and prepares it for use in this test.
327
+ // The client can be used like a normal Client object, but maintains its state across tests.
328
+ // Returns nil if the client doesn't exist.
329
+ func (t * T ) GetSharedClient (clientID string ) * Client {
330
+ if t .suite == nil || t .suite .SharedClients == nil {
331
+ t .Logf ("No shared clients available in this suite" )
332
+ return nil
333
+ }
334
+
335
+ sharedClient , exists := t .suite .SharedClients [clientID ]
336
+ if ! exists {
337
+ t .Logf ("Shared client %q not found" , clientID )
338
+ return nil
339
+ }
340
+
341
+ // Store the test context in the client so it can be used for this test
342
+ // Create a new Client instance that points to the same container
343
+ client := & Client {
344
+ Type : sharedClient .Type ,
345
+ Container : sharedClient .Container ,
346
+ IP : sharedClient .IP ,
347
+ test : t ,
348
+ IsShared : true ,
349
+ SuiteID : t .SuiteID ,
350
+ }
351
+
352
+ // Initialize log tracking for this client
353
+ if t .clientLogOffsets == nil {
354
+ t .clientLogOffsets = make (map [string ]* LogOffset )
355
+ }
356
+
357
+ // Record the current log position for this client
358
+ t .clientLogOffsets [clientID ] = & LogOffset {
359
+ Start : sharedClient .LogPosition ,
360
+ End : 0 , // Will be set when the test completes
361
+ }
362
+
363
+ return client
239
364
}
240
365
241
366
// RunClient runs the given client test against a single client type.
@@ -365,15 +490,48 @@ func runTest(host *Simulation, test testSpec, runit func(t *T)) error {
365
490
Sim : host ,
366
491
SuiteID : test .suiteID ,
367
492
suite : test .suite ,
493
+ clientLogOffsets : make (map [string ]* LogOffset ), // Initialize log offset tracking
368
494
}
369
495
testID , err := host .StartTest (test .suiteID , test .request ())
370
496
if err != nil {
371
497
return err
372
498
}
373
499
t .TestID = testID
374
500
t .result .Pass = true
501
+
502
+ // Capture current log positions for all shared clients before running the test
503
+ if test .suite != nil && test .suite .SharedClients != nil {
504
+ for clientID , client := range test .suite .SharedClients {
505
+ // Get the current log position for each shared client
506
+ logPosition , err := host .GetClientLogOffset (test .suiteID , client .Container )
507
+ if err == nil {
508
+ t .clientLogOffsets [clientID ] = & LogOffset {
509
+ Start : logPosition ,
510
+ End : 0 , // Will be set when test completes
511
+ }
512
+ }
513
+ }
514
+ }
515
+
375
516
defer func () {
376
517
t .mu .Lock ()
518
+
519
+ // After test is complete, update ending log positions for all shared clients
520
+ if test .suite != nil && test .suite .SharedClients != nil {
521
+ for clientID , client := range test .suite .SharedClients {
522
+ if offset , exists := t .clientLogOffsets [clientID ]; exists {
523
+ // Get the current log position after test execution
524
+ logPosition , err := host .GetClientLogOffset (test .suiteID , client .Container )
525
+ if err == nil {
526
+ offset .End = logPosition
527
+
528
+ // Update the shared client's log position for the next test
529
+ client .LogPosition = logPosition
530
+ }
531
+ }
532
+ }
533
+ }
534
+
377
535
defer t .mu .Unlock ()
378
536
host .EndTest (test .suiteID , testID , t .result )
379
537
}()
0 commit comments