-
Notifications
You must be signed in to change notification settings - Fork 311
Refactor Test TDS Servers. Expand functional testing of connection pooling and transient failures. #3488
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…ailover-tds-server
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me, with a few suggestions.
initialServer.SetErrorBehavior(true, errorCode, "Transient fault occurred."); | ||
using SqlConnection failoverConnection = new(builder.ConnectionString); | ||
// Should fail over to the failover server | ||
failoverConnection.Open(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we need to Assert() that the connection connected where we expect it to? Something like:
Assert.Equal(failoverConnection.RemoteEndpoint, failoverServer.Endpoint);
@@ -310,8 +397,14 @@ public void ConnectionTestValidCredentialCombination() | |||
public void ConnectionTimeoutTest(int timeout) | |||
{ | |||
// Start a server with connection timeout from the inline data. | |||
using TestTdsServer server = TestTdsServer.StartTestServer(false, false, timeout); | |||
using SqlConnection connection = new SqlConnection(server.ConnectionString); | |||
//TODO: do we even need a server for this test? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO we shouldn't need a server for most of these tests. We're missing a way to intercept outbound TDS packets and inject inbound TDS packets. If we had that, we wouldn't need TdsServer for these kinds of unit tests, and we could clearly see in each test exactly what was being tested. Right now, it isn't obvious what TdsServer is doing or how it is verifying our expectations. This is something I think we need to work towards - nothing for this PR to address.
Assert.Equal(ConnectionState.Open, connection.State); | ||
|
||
// Failures should prompt the client to return to the original server, resulting in a login count of 2 | ||
Assert.Equal(2, router.PreLoginCount); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We haven't verified what Open() actually did though - just what the side-effects on our test servers were. Did Open() make a bunch of unexpected failed connection attempts elsewhere? Which test server, if any, is it currently connected to? We don't know.
src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/AuthenticatingTDSServer.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTDSServer.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTDSServer.cs
Outdated
Show resolved
Hide resolved
public TdsServer() : this(new TdsServerArguments()) | ||
{ | ||
} | ||
/// <summary> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing a blank line here.
/// <summary> | ||
/// Constructor with arguments | ||
/// </summary> | ||
public TdsServer(TdsServerArguments arguments) : base(arguments) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You won't need the default constructor if you:
public TdsServer(TdsServerArguments arguments = new()) : base(arguments)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
default arguments need to be compile time constant or a ValType.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Frown - that's an unfortunate restriction. Can TdsServerArguments be a struct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not easily. You can't inherit from a struct, so we would have to flatten all of these server-type specific arguments down into one flat struct. It would work but wouldn't be super usable.
…ut. Clean up transient error server.
…p where they're supposed to, not just side effects.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few tidying-up suggestions.
@@ -79,7 +80,7 @@ public abstract class ServerEndPointConnection | |||
/// <summary> | |||
/// The flag indicates whether server is being stopped |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This property isn't really a flag any more.
/// <summary> | ||
/// Handler for login request | ||
/// </summary> | ||
/// <summary> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra indent.
/// <summary> | ||
/// Constructor with arguments | ||
/// </summary> | ||
public TdsServer(TdsServerArguments arguments) : base(arguments) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Frown - that's an unfortunate restriction. Can TdsServerArguments be a struct?
/// </summary> | ||
public override TDSMessageCollection OnLogin7Request(ITDSServerSession session, TDSMessage request) | ||
{ | ||
// Check if we're still going to raise transient error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How/where does this cause a transient error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this server is intended to delay responses, eliciting a driver-side timeout. So this isn't really a timeout server - it's a delay server.
You could also make the count of delayed requests configurable instead of hardcoding it to 1. Then you could test a variety of retry counts/mechanisms in the driver.
/// </summary> | ||
public TransientTimeoutTdsServerArguments() | ||
{ | ||
SleepDuration = TimeSpan.FromSeconds(0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you just set these on the properties, and avoid a constructor?
public TimeSpan SleepDuration { get; set; } = TimeSpan.FromSeconds(0);
That makes it easy to see the default value for each property.
…ailover-tds-server
Description
The TDS.Servers project provides mocked server implementations for a variety of use cases. The construction and endpoint initialization flows for these mock servers was complicated and resulted in duplicated code across TDS.Servers, FunctionalTests, and ManualTests. This PR consolidates endpoint initialization and makes test server constructor parameters more explicit (no more defaults buried a few layers deep).
One downside of this approach is that it's now slightly harder to get the connection string for a test server. Before, some servers had a ConnectionString property you could access. The issue with these connection strings is they had a lot of non-standard default behavior coded into them at the time they were constructed. This makes it hard to understand which properties you're actually using when connecting to a test server. With my changes, you now need to manually construct a connection string using SqlConnectionStringBuilder and explicitly set connection parameters. Any parameters you don't set use the stock connection string defaults.
Testing
I added some testing around pool invalidation during failover and routing behavior during transient errors.