1- using Medallion . Shell ;
2- using StackExchange . Redis ;
1+ using StackExchange . Redis ;
2+ using Testcontainers . Redis ;
33
44namespace Medallion . Threading . Tests . Redis ;
55
@@ -8,33 +8,29 @@ internal class RedisServer
88 // redis default is 6379, so go one above that
99 private static readonly int MinDynamicPort = RedisPorts . DefaultPorts . Max ( ) + 1 , MaxDynamicPort = MinDynamicPort + 100 ;
1010
11- // it's important for this to be lazy because it doesn't work when running on Linux
12- private static readonly Lazy < string > WslPath = new (
13- ( ) => Directory . GetDirectories ( @"C:\Windows\WinSxS" )
14- . Select ( d => Path . Combine ( d , "wsl.exe" ) )
15- . Where ( File . Exists )
16- . OrderByDescending ( File . GetCreationTimeUtc )
17- . First ( )
18- ) ;
11+ public static async Task DisposeAsync ( )
12+ {
13+ foreach ( var container in RedisContainers )
14+ {
15+ await container . StopAsync ( ) ;
16+ }
17+ }
1918
20- private static readonly Dictionary < int , RedisServer > ActiveServersByPort = [ ] ;
19+ private static readonly List < RedisContainer > RedisContainers = [ ] ;
2120 private static readonly RedisServer [ ] DefaultServers = new RedisServer [ RedisPorts . DefaultPorts . Count ] ;
2221
23- private readonly Command _command ;
22+ private readonly RedisContainer _redis ;
2423
25- public RedisServer ( bool allowAdmin = false ) : this ( null , allowAdmin ) { }
26-
27- private RedisServer ( int ? port , bool allowAdmin )
24+ public RedisServer ( bool allowAdmin = false )
2825 {
29- lock ( ActiveServersByPort )
30- {
31- this . Port = port ?? Enumerable . Range ( MinDynamicPort , count : MaxDynamicPort - MinDynamicPort + 1 )
32- . First ( p => ! ActiveServersByPort . ContainsKey ( p ) ) ;
33- this . _command = Command . Run ( WslPath . Value , [ "redis-server" , "--port" , this . Port ] , options : o => o . StartInfo ( si => si . RedirectStandardInput = false ) )
34- . RedirectTo ( Console . Out )
35- . RedirectStandardErrorTo ( Console . Error ) ;
36- ActiveServersByPort . Add ( this . Port , this ) ;
37- }
26+ _redis = new RedisBuilder ( )
27+ . WithPortBinding ( MinDynamicPort + RedisContainers . Count )
28+ . Build ( ) ;
29+ _redis . StartAsync ( ) . Wait ( ) ;
30+ RedisContainers . Add ( _redis ) ;
31+
32+ this . Port = _redis . GetMappedPublicPort ( RedisBuilder . RedisPort ) ;
33+
3834 this . Multiplexer = ConnectionMultiplexer . Connect ( $ "localhost:{ this . Port } ,abortConnect=false{ ( allowAdmin ? ",allowAdmin=true" : string . Empty ) } ") ;
3935 // Clean the db to ensure it is empty. Running an arbitrary command also ensures that
4036 // the db successfully spun up before we proceed (Connect seemingly can complete before that happens).
@@ -43,49 +39,16 @@ private RedisServer(int? port, bool allowAdmin)
4339 this . Multiplexer . GetDatabase ( ) . Execute ( "flushall" , Array . Empty < object > ( ) , CommandFlags . DemandMaster ) ;
4440 }
4541
46- public int ProcessId => this . _command . ProcessId ;
4742 public int Port { get ; }
4843 public ConnectionMultiplexer Multiplexer { get ; }
4944
45+ public void Dispose ( ) => _redis . DisposeAsync ( ) . GetAwaiter ( ) . GetResult ( ) ;
46+
5047 public static RedisServer GetDefaultServer ( int index )
5148 {
5249 lock ( DefaultServers )
5350 {
54- return DefaultServers [ index ] ??= new RedisServer ( RedisPorts . DefaultPorts [ index ] , allowAdmin : false ) ;
55- }
56- }
57-
58- public static void DisposeAll ( )
59- {
60- lock ( ActiveServersByPort )
61- {
62- var shutdownTasks = ActiveServersByPort . Values
63- . Select ( async server =>
64- {
65- // When testing the case of a server outage, we'll have manually shut down some servers.
66- // In that case, we shouldn't attempt to connect to them since that will fail.
67- var isConnected = server . Multiplexer . GetServers ( ) . Any ( s => s . IsConnected ) ;
68- server . Multiplexer . Dispose ( ) ;
69- try
70- {
71- if ( isConnected )
72- {
73- using var adminMultiplexer = await ConnectionMultiplexer . ConnectAsync ( $ "localhost:{ server . Port } ,allowAdmin=true") ;
74- adminMultiplexer . GetServer ( "localhost" , server . Port ) . Shutdown ( ShutdownMode . Never ) ;
75- }
76- }
77- finally
78- {
79- if ( ! await server . _command . Task . TryWaitAsync ( TimeSpan . FromSeconds ( 5 ) ) )
80- {
81- server . _command . Kill ( ) ;
82- throw new InvalidOperationException ( "Forced to kill Redis server" ) ;
83- }
84- }
85- } )
86- . ToArray ( ) ;
87- ActiveServersByPort . Clear ( ) ;
88- Task . WaitAll ( shutdownTasks ) ;
51+ return DefaultServers [ index ] ??= new RedisServer ( allowAdmin : false ) ;
8952 }
9053 }
9154}
0 commit comments