-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Add missing tests for NamedPipes #72956
Conversation
Tagging subscribers to this area: @dotnet/area-system-io Issue DetailsSome tests were missing on PipeOptions.CurrentUserOnly scenarios.
|
My original comment on these tests (for Windows) so I can page back in:
|
src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CurrentUserOnly.Windows.cs
Show resolved
Hide resolved
}); | ||
}); | ||
|
||
Thread.Sleep(1000); // give it a sec to allow above tasks to perform some work before this. |
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've gotten pretty suspicious of sleeps in tests! I guess it's OK since the test won't fail on a slow machine, but the ideal pattern is to wait on some event set in the task eg after it's looped.
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.
in fact, rather than having one task looping, you could consider kicking off N tasks all trying to connect once. That will give you some concurrency, and you can just do Task.WaitAll for them to complete, no sleep and no event.
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.
the WaitAll would be after the await serverWait;
.
As an improvement, I suggest to have the tasks all wait on an event before they try to connect, and then set that event on the line before you do client.Connect()
. That will make it most likely they do their work concurrently with the succesful connect.
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.
+1
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.
With serverWait = server.WaitForConnectionAsync()
, we can't do anything after awaiting it, the method blocks until a client connects; the event should be set before awaiting serverWait. Please check latest commit.
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 sure I follow. How is 100 better than 1? The ideal is to not have any guesses.
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'n not very good explaining so, here's how the test would look like:
[OuterLoop]
[ConditionalFact(nameof(IsAdminOnSupportedWindowsVersions))]
public async Task Connection_UnderDifferentUsers_CurrentUserOnlyOnServer_InvalidClientConnectionAttempts_DoNotBlockSuccessfulClient()
{
string name = PipeStreamConformanceTests.GetUniquePipeName();
bool invalidClientShouldStop = false;
+ int invalidClientAttempts = 0;
using var server = new NamedPipeServerStream(name, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly);
Task serverWait = server.WaitForConnectionAsync();
Task invalidClient = Task.Run(() =>
{
// invalid non-current user tries to connect to server.
_testAccountImpersonator.RunImpersonated(() =>
{
while (!Volatile.Read(ref invalidClientShouldStop))
{
using var client = new NamedPipeClientStream(".", name, PipeDirection.In, PipeOptions.Asynchronous);
Assert.Throws<UnauthorizedAccessException>(() => client.Connect());
+ Volatile.Write(ref invalidClientAttempts, invalidClientAttempts + 1);
}
});
});
- Thread.Sleep(1000); // give it a sec to allow above tasks to perform some work before this.
+ while (Volatile.Read(ref invalidClientAttempts) < 100) ; // Wait until the invalid client has tried multiple times.
Assert.False(serverWait.IsCompleted);
// valid client tries to connect and succeeds.
using var client = new NamedPipeClientStream(".", name, PipeDirection.In, PipeOptions.Asynchronous);
client.Connect();
await serverWait;
Volatile.Write(ref invalidClientShouldStop, true);
}
This way we can at least ensure that the other two threads were running prior to the valid client thread, and it won't take 1 second, will take whatever 100 iterations take, which is probably less.
100 was just a placeholder, it can be 20 or 5 or 1.
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.
What I am arguing is that we can have the loop with the event, and without the thread.sleep.
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.
Yes to the loop without the sleep, but what I'm asking is why you need a counter. As long as it's gone through the loop once, you know the "interference" is in progress, right? We don't expect any new behavior to emerge after 100 times.
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.
Yes, I agree there's no need for 100 times.
src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CurrentUserOnly.Windows.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CurrentUserOnly.Windows.cs
Show resolved
Hide resolved
src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CurrentUserOnly.Windows.cs
Outdated
Show resolved
Hide resolved
Should we also have Windows tests where there are different users on each end, and neither is elevated? this would probably be easiest to do by creating two different users. |
src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CurrentUserOnly.Unix.cs
Outdated
Show resolved
Hide resolved
}); | ||
}); | ||
|
||
Thread.Sleep(1000); // give it a sec to allow above tasks to perform some work before this. |
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.
+1
It is doable, the My point is that it may be too cumbersome for this PR. I also don't think there's too much value on it. |
Actually, we can create the second TestAccountImpersonator inside the test method, that should be easier. We just need to make sure that the theory data does not create a new one everytime. |
src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CurrentUserOnly.Windows.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CurrentUserOnly.Unix.cs
Outdated
Show resolved
Hide resolved
}); | ||
|
||
using var server = new NamedPipeServerStream(name, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly); | ||
@event.Set(); |
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.
There's a very high liklihood that both Connects will be initiated before the server begins waiting. There's also a high liklihood that the valid client will connect before the invalid one does.
In the original test I'd proposed, the invalid client was looping, trying to connect over and over, and only once it started that loop would the valid client try to connect, the goal of the test being to ensure the invalid client couldn't starve out the valid client. That's no longer happening here, so I'm not sure what this test is actually testing. With the invalid client making a single connection attempt, eventually that attempt will end and the valid client can connect. And if that were broken, that would be caught by just a simple test with no client/client concurrency that simply tried to connect from an invalid client and then sequentially tried to connect from a valid client.
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 can bring the original test back and keep this version as well.
fwiw: this was the old version that issued invalid connects in a loop:
c94d060#diff-23b3a1e5a562e7712c4adff12019bbc8485dd0566627dc77808efa97efee2b30R134
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 agree that this test is not worth keeping, because it will so rarely do anything useful. If you can tweak the test you brought back below, I think it will do it correctly, and you can delete this.
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.
It's almost ready, thank you for improving the tests @jozkee !
{ | ||
bool isRoot = AdminHelpers.IsProcessElevated(); |
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 test is executed only for "super user":
runtime/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CurrentUserOnly.Unix.cs
Lines 26 to 28 in c1fab9c
private static bool IsRemoteExecutorSupportedAndOnUnixAndSuperUser => RemoteExecutor.IsSupported && PlatformDetection.IsUnixAndSuperUser; | |
[ConditionalTheory(nameof(IsRemoteExecutorSupportedAndOnUnixAndSuperUser))] |
runtime/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.Unix.cs
Line 58 in d196d85
public static bool IsSuperUser => IsBrowser || IsWindows ? false : libc.geteuid() == 0; |
and the helper that checks whether the process is elevated performs same check:
public static unsafe bool IsProcessElevated() | |
{ | |
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) | |
{ | |
uint userId = Interop.Sys.GetEUid(); | |
return(userId == 0); |
It seems that this check is redundant and adding PipeOptions.CurrentUserOnly
test cases has no effect?
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.
IIRC, the reason I did this was to complete the test matrix and have explicit behaviors of all combinations. But this could be represented by just having the theorydata where clientPipeOptions = CurrentUserOnly commented-out.
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.
@jozkee IMO it would be better to remove the test cases that are always skipped and just add a comment to the test explaining why given scenarios are not covered.
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.
Actually, the right thing to do is remove IsRemoteExecutorSupportedAndOnUnixAndSuperUser
that way we can run all the inline data when dotnet is not running as sudo/root and skip the clientPipeOptions == PipeOptions.CurrentUserOnly
when it is.
This worked as I just described on my local wsl, I hope it works in CI as well.
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.
@jozkee but we need to keep the IsRemoteExecutorSupported
condition as the test is using it.
src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CurrentUserOnly.Windows.cs
Show resolved
Hide resolved
/azp run runtime-libraries-coreclr outerloop |
Azure Pipelines successfully started running 1 pipeline(s). |
CI errors are #76755 |
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.
LGTM, thank you @jozkee !
Some tests were missing on PipeOptions.CurrentUserOnly scenarios.
This PR extend the test to account for the following (under the OS defaults, which allows readonly access on different users):