33using System . IO . Pipelines ;
44using System . Net ;
55using System . Net . Sockets ;
6+ using System . Text ;
67using System . Threading ;
78using System . Threading . Tasks ;
89using Pipelines . Sockets . Unofficial ;
@@ -14,17 +15,79 @@ namespace StackExchange.Redis.Tests;
1415
1516public class InProcessTestServer : MemoryCacheRedisServer
1617{
17- public Tunnel Tunnel { get ; }
18-
1918 private readonly ITestOutputHelper ? _log ;
2019 public InProcessTestServer ( ITestOutputHelper ? log = null )
2120 {
21+ RedisVersion = RedisFeatures . v6_0_0 ; // for client to expect RESP3
2222 _log = log ;
2323 // ReSharper disable once VirtualMemberCallInConstructor
2424 _log ? . WriteLine ( $ "Creating in-process server: { ToString ( ) } ") ;
2525 Tunnel = new InProcTunnel ( this ) ;
2626 }
2727
28+ public Task < ConnectionMultiplexer > ConnectAsync ( bool withPubSub = false , TextWriter ? log = null )
29+ => ConnectionMultiplexer . ConnectAsync ( GetClientConfig ( withPubSub ) , log ) ;
30+
31+ public ConfigurationOptions GetClientConfig ( bool withPubSub = false )
32+ {
33+ var commands = GetCommands ( ) ;
34+ if ( ! withPubSub )
35+ {
36+ commands . Remove ( nameof ( RedisCommand . SUBSCRIBE ) ) ;
37+ commands . Remove ( nameof ( RedisCommand . PSUBSCRIBE ) ) ;
38+ commands . Remove ( nameof ( RedisCommand . SSUBSCRIBE ) ) ;
39+ commands . Remove ( nameof ( RedisCommand . UNSUBSCRIBE ) ) ;
40+ commands . Remove ( nameof ( RedisCommand . PUNSUBSCRIBE ) ) ;
41+ commands . Remove ( nameof ( RedisCommand . SUNSUBSCRIBE ) ) ;
42+ commands . Remove ( nameof ( RedisCommand . PUBLISH ) ) ;
43+ commands . Remove ( nameof ( RedisCommand . SPUBLISH ) ) ;
44+ }
45+ // transactions don't work yet
46+ commands . Remove ( nameof ( RedisCommand . MULTI ) ) ;
47+ commands . Remove ( nameof ( RedisCommand . EXEC ) ) ;
48+ commands . Remove ( nameof ( RedisCommand . DISCARD ) ) ;
49+ commands . Remove ( nameof ( RedisCommand . WATCH ) ) ;
50+ commands . Remove ( nameof ( RedisCommand . UNWATCH ) ) ;
51+
52+ var config = new ConfigurationOptions
53+ {
54+ CommandMap = CommandMap . Create ( commands ) ,
55+ ConfigurationChannel = "" ,
56+ TieBreaker = "" ,
57+ DefaultVersion = RedisVersion ,
58+ ConnectTimeout = 10000 ,
59+ SyncTimeout = 5000 ,
60+ AsyncTimeout = 5000 ,
61+ AllowAdmin = true ,
62+ Tunnel = Tunnel ,
63+ } ;
64+ foreach ( var endpoint in GetEndPoints ( ) )
65+ {
66+ config . EndPoints . Add ( endpoint ) ;
67+ }
68+ return config ;
69+ }
70+
71+ public Tunnel Tunnel { get ; }
72+
73+ public override void Log ( string message )
74+ {
75+ _log ? . WriteLine ( message ) ;
76+ base . Log ( message ) ;
77+ }
78+
79+ protected override void OnMoved ( RedisClient client , int hashSlot , Node node )
80+ {
81+ _log ? . WriteLine ( $ "Client { client . Id } being redirected: { hashSlot } to { node } ") ;
82+ base . OnMoved ( client , hashSlot , node ) ;
83+ }
84+
85+ public override TypedRedisValue OnUnknownCommand ( in RedisClient client , in RedisRequest request , ReadOnlySpan < byte > command )
86+ {
87+ _log ? . WriteLine ( $ "[{ client . Id } ] unknown command: { Encoding . ASCII . GetString ( command ) } ") ;
88+ return base . OnUnknownCommand ( in client , in request , command ) ;
89+ }
90+
2891 private sealed class InProcTunnel (
2992 InProcessTestServer server ,
3093 PipeOptions ? pipeOptions = null ) : Tunnel
@@ -33,8 +96,12 @@ private sealed class InProcTunnel(
3396 EndPoint endpoint ,
3497 CancellationToken cancellationToken )
3598 {
36- // server._log?.WriteLine($"Disabling client creation, requested endpoint: {Format.ToString(endpoint)}");
37- return default ;
99+ if ( server . TryGetNode ( endpoint , out _ ) )
100+ {
101+ // server._log?.WriteLine($"Disabling client creation, requested endpoint: {Format.ToString(endpoint)}");
102+ return default ;
103+ }
104+ return base . GetSocketConnectEndpointAsync ( endpoint , cancellationToken ) ;
38105 }
39106
40107 public override ValueTask < Stream ? > BeforeAuthenticateAsync (
@@ -43,13 +110,18 @@ private sealed class InProcTunnel(
43110 Socket ? socket ,
44111 CancellationToken cancellationToken )
45112 {
46- server . _log ? . WriteLine ( $ "Client intercepted, requested endpoint: { Format . ToString ( endpoint ) } for { connectionType } usage") ;
47- var clientToServer = new Pipe ( pipeOptions ?? PipeOptions . Default ) ;
48- var serverToClient = new Pipe ( pipeOptions ?? PipeOptions . Default ) ;
49- var serverSide = new Duplex ( clientToServer . Reader , serverToClient . Writer ) ;
50- _ = Task . Run ( async ( ) => await server . RunClientAsync ( serverSide ) , cancellationToken ) ;
51- var clientSide = StreamConnection . GetDuplex ( serverToClient . Reader , clientToServer . Writer ) ;
52- return new ( clientSide ) ;
113+ if ( server . TryGetNode ( endpoint , out var node ) )
114+ {
115+ server . _log ? . WriteLine (
116+ $ "Client intercepted, endpoint { Format . ToString ( endpoint ) } ({ connectionType } ) mapped to { server . ServerType } node { node } ") ;
117+ var clientToServer = new Pipe ( pipeOptions ?? PipeOptions . Default ) ;
118+ var serverToClient = new Pipe ( pipeOptions ?? PipeOptions . Default ) ;
119+ var serverSide = new Duplex ( clientToServer . Reader , serverToClient . Writer ) ;
120+ _ = Task . Run ( async ( ) => await server . RunClientAsync ( serverSide , node : node ) , cancellationToken ) ;
121+ var clientSide = StreamConnection . GetDuplex ( serverToClient . Reader , clientToServer . Writer ) ;
122+ return new ( clientSide ) ;
123+ }
124+ return base . BeforeAuthenticateAsync ( endpoint , connectionType , socket , cancellationToken ) ;
53125 }
54126
55127 private sealed class Duplex ( PipeReader input , PipeWriter output ) : IDuplexPipe
@@ -65,6 +137,7 @@ public ValueTask Dispose()
65137 }
66138 }
67139 }
140+
68141 /*
69142
70143 private readonly RespServer _server;
0 commit comments