Skip to content

Commit dd8bb81

Browse files
authored
Merge pull request #271 from bgrainger/fake-server
Add fake MySQL Server for tests.
2 parents 2d54287 + 27ac3a6 commit dd8bb81

File tree

6 files changed

+413
-57
lines changed

6 files changed

+413
-57
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System.IO;
2+
using System.Text;
3+
4+
namespace MySqlConnector.Tests
5+
{
6+
public static class BinaryWriterExtensions
7+
{
8+
public static void WriteRaw(this BinaryWriter writer, string value) => writer.Write(Encoding.UTF8.GetBytes(value));
9+
10+
public static void WriteNullTerminated(this BinaryWriter writer, string value)
11+
{
12+
writer.Write(Encoding.UTF8.GetBytes(value));
13+
writer.Write((byte) 0);
14+
}
15+
}
16+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using MySql.Data.MySqlClient;
6+
using Xunit;
7+
8+
namespace MySqlConnector.Tests
9+
{
10+
public class ConnectionTests : IDisposable
11+
{
12+
public ConnectionTests()
13+
{
14+
m_server = new FakeMySqlServer();
15+
m_server.Start();
16+
17+
m_csb = new MySqlConnectionStringBuilder
18+
{
19+
Server = "localhost",
20+
Port = (uint) m_server.Port,
21+
};
22+
}
23+
24+
public void Dispose()
25+
{
26+
m_server.Stop();
27+
}
28+
29+
[Fact]
30+
public void PooledConnectionIsReturnedToPool()
31+
{
32+
Assert.Equal(0, m_server.ActiveConnections);
33+
34+
m_csb.Pooling = true;
35+
using (var connection = new MySqlConnection(m_csb.ConnectionString))
36+
{
37+
connection.Open();
38+
Assert.Equal(1, m_server.ActiveConnections);
39+
40+
Assert.Equal(m_server.ServerVersion, connection.ServerVersion);
41+
connection.Close();
42+
Assert.Equal(1, m_server.ActiveConnections);
43+
}
44+
45+
Assert.Equal(1, m_server.ActiveConnections);
46+
}
47+
48+
[Fact]
49+
public async Task UnpooledConnectionIsClosed()
50+
{
51+
Assert.Equal(0, m_server.ActiveConnections);
52+
53+
m_csb.Pooling = false;
54+
using (var connection = new MySqlConnection(m_csb.ConnectionString))
55+
{
56+
await connection.OpenAsync();
57+
Assert.Equal(1, m_server.ActiveConnections);
58+
59+
Assert.Equal(m_server.ServerVersion, connection.ServerVersion);
60+
connection.Close();
61+
62+
await WaitForConditionAsync(0, () => m_server.ActiveConnections);
63+
}
64+
}
65+
66+
[Theory]
67+
[InlineData(2u, 3u, true)]
68+
[InlineData(180u, 3u, false)]
69+
public async Task ConnectionLifeTime(uint lifeTime, uint delaySeconds, bool shouldTimeout)
70+
{
71+
m_csb.Pooling = true;
72+
m_csb.MinimumPoolSize = 0;
73+
m_csb.MaximumPoolSize = 1;
74+
m_csb.ConnectionLifeTime = lifeTime;
75+
int serverThread;
76+
77+
using (var connection = new MySqlConnection(m_csb.ConnectionString))
78+
{
79+
await connection.OpenAsync();
80+
serverThread = connection.ServerThread;
81+
await Task.Delay(TimeSpan.FromSeconds(delaySeconds));
82+
}
83+
84+
using (var connection = new MySqlConnection(m_csb.ConnectionString))
85+
{
86+
await connection.OpenAsync();
87+
if (shouldTimeout)
88+
Assert.NotEqual(serverThread, connection.ServerThread);
89+
else
90+
Assert.Equal(serverThread, connection.ServerThread);
91+
}
92+
}
93+
94+
[Fact]
95+
public void LeakReaders()
96+
{
97+
m_csb.Pooling = true;
98+
m_csb.MinimumPoolSize = 0;
99+
m_csb.MaximumPoolSize = 6;
100+
m_csb.ConnectionTimeout = 30u;
101+
102+
for (var i = 0; i < m_csb.MaximumPoolSize + 2; i++)
103+
{
104+
var connection = new MySqlConnection(m_csb.ConnectionString);
105+
connection.Open();
106+
107+
var cmd = connection.CreateCommand();
108+
cmd.CommandText = "SELECT 1;";
109+
var reader = cmd.ExecuteReader();
110+
Assert.True(reader.Read());
111+
112+
// have to GC for leaked connections to be removed from the pool
113+
GC.Collect();
114+
115+
// HACK: have to sleep (so that RecoverLeakedSessions is called in ConnectionPool.GetSessionAsync)
116+
Thread.Sleep(250);
117+
}
118+
}
119+
120+
private static async Task WaitForConditionAsync<T>(T expected, Func<T> getValue)
121+
{
122+
var sw = Stopwatch.StartNew();
123+
while (sw.ElapsedMilliseconds < 1000 && !expected.Equals(getValue()))
124+
await Task.Delay(50);
125+
Assert.Equal(expected, getValue());
126+
}
127+
128+
readonly FakeMySqlServer m_server;
129+
readonly MySqlConnectionStringBuilder m_csb;
130+
}
131+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Net;
4+
using System.Net.Sockets;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
8+
namespace MySqlConnector.Tests
9+
{
10+
public sealed class FakeMySqlServer
11+
{
12+
public FakeMySqlServer()
13+
{
14+
m_tcpListener = new TcpListener(IPAddress.Any, 0);
15+
m_tasks = new List<Task>();
16+
}
17+
18+
public void Start()
19+
{
20+
m_activeConnections = 0;
21+
m_cts = new CancellationTokenSource();
22+
m_tcpListener.Start();
23+
m_tasks.Add(AcceptConnectionsAsync());
24+
}
25+
26+
public void Stop()
27+
{
28+
m_cts.Cancel();
29+
m_tcpListener.Stop();
30+
try
31+
{
32+
Task.WaitAll(m_tasks.ToArray());
33+
}
34+
catch (AggregateException)
35+
{
36+
}
37+
m_tasks.Clear();
38+
m_cts.Dispose();
39+
m_cts = null;
40+
}
41+
42+
public int Port => ((IPEndPoint) m_tcpListener.LocalEndpoint).Port;
43+
44+
public int ActiveConnections => m_activeConnections;
45+
46+
public string ServerVersion { get; set; } = "5.7.10-test";
47+
48+
internal void ClientDisconnected() => Interlocked.Decrement(ref m_activeConnections);
49+
50+
private async Task AcceptConnectionsAsync()
51+
{
52+
while (true)
53+
{
54+
var tcpClient = await m_tcpListener.AcceptTcpClientAsync();
55+
Interlocked.Increment(ref m_activeConnections);
56+
lock (m_tasks)
57+
{
58+
var connection = new FakeMySqlServerConnection(this, m_tasks.Count);
59+
m_tasks.Add(connection.RunAsync(tcpClient, m_cts.Token));
60+
}
61+
}
62+
}
63+
64+
readonly TcpListener m_tcpListener;
65+
readonly List<Task> m_tasks;
66+
CancellationTokenSource m_cts;
67+
int m_activeConnections;
68+
}
69+
}

0 commit comments

Comments
 (0)