Skip to content

Feature/executeasync #937

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

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/.idea/.idea.Renci.SshNet.VS2019/.idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/.idea/.idea.Renci.SshNet.VS2019/.idea/.name

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions src/.idea/.idea.Renci.SshNet.VS2019/.idea/deployment.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions src/.idea/.idea.Renci.SshNet.VS2019/.idea/indexLayout.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/.idea/.idea.Renci.SshNet.VS2019/.idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/.idea/.idea.Renci.SshNet.VS2019/.idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions src/Renci.SshNet.Tests/Classes/SshClientTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
using System.IO;
using System.Text;
using System.Linq;
#if FEATURE_TAP
using System.Threading;
using System.Threading.Tasks;
#endif

namespace Renci.SshNet.Tests.Classes
{
Expand Down Expand Up @@ -35,6 +39,28 @@ public void Test_Connect_Using_Correct_Password()
#endregion
}

#if FEATURE_TAP
[TestMethod]
[TestCategory("Authentication")]
[TestCategory("integration")]
public async Task Test_ConnectAsync_Using_Correct_Password()
{
var host = Resources.HOST;
var username = Resources.USERNAME;
var password = Resources.PASSWORD;

#region Example SshClient(host, username) Connect
using (var client = new SshClient(host, username, password))
{
var cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(60)).Token;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When CancellationTokenSource is used with timeout, it should be disposed, otherwise Timers will pile up. SOmething like:

using (var cts = new CancellationTokenSource(timeout))
{
    await SomethingAsync(cts.Token);
}

await client.ConnectAsync(cancellationToken);
// Do something here
client.Disconnect();
}
#endregion
}
#endif

[TestMethod]
[TestCategory("Authentication")]
[TestCategory("integration")]
Expand Down
94 changes: 93 additions & 1 deletion src/Renci.SshNet.Tests/Classes/SshCommandTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,71 @@ public void Test_Run_SingleCommand()
}
}

#if FEATURE_TAP
[TestMethod]
[TestCategory("integration")]
public async Task Test_Run_SingleCommandAsync()
{
var host = Resources.HOST;
var username = Resources.USERNAME;
var password = Resources.PASSWORD;

using (var client = new SshClient(host, username, password))
{
#region Example SshCommand RunCommand Result Async
var cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(60)).Token;
await client.ConnectAsync(cancellationToken);

var testValue = Guid.NewGuid().ToString();
var command = await client.RunCommandAsync(string.Format("echo {0}", testValue), cancellationToken);
var result = await new StreamReader(command.OutputStream).ReadToEndAsync();
result = result.Substring(0, result.Length - 1); // Remove \n character returned by command

client.Disconnect();
#endregion

Assert.IsTrue(result.Equals(testValue));
}
}

[TestMethod]
[TestCategory("integration")]
public async Task Test_Run_SingleCommandAsync_WithShortTimeout()
{
var host = Resources.HOST;
var username = Resources.USERNAME;
var password = Resources.PASSWORD;

using (var client = new SshClient(host, username, password))
{
#region Example SshCommand RunCommand Result Async With Short Timeout
var cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(60)).Token;
await client.ConnectAsync(cancellationToken);

// Timeout in 100ms
var shortTimeout = new CancellationTokenSource(TimeSpan.FromMilliseconds(100)).Token;

var testValue = Guid.NewGuid().ToString();
string result;
try
{
var command = await client.RunCommandAsync(string.Format("echo {0};/bin/sleep 5", testValue), shortTimeout);
result = await new StreamReader(command.OutputStream).ReadToEndAsync();
result = result.Substring(0, result.Length - 1); // Remove \n character returned by command
}
catch (OperationCanceledException)
{
result = "canceled";
}

client.Disconnect();
#endregion

Assert.IsTrue(result.Equals("canceled"));
}
}
#endif

[TestMethod]
[TestCategory("integration")]
public void Test_Execute_SingleCommand()
Expand Down Expand Up @@ -266,7 +331,7 @@ public void Test_Execute_Command_ExitStatus()
client.Connect();

var cmd = client.RunCommand("exit 128");

Console.WriteLine(cmd.ExitStatus);

client.Disconnect();
Expand Down Expand Up @@ -500,6 +565,33 @@ public void Test_Execute_Invalid_Command()
}
}

#if FEATURE_TAP
[TestMethod]
[TestCategory("integration")]
public async Task Test_Execute_Invalid_CommandAsync()
{
using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
{
#region Example SshCommand CreateCommand Error Async

await client.ConnectAsync(default);

var cmd = client.CreateCommand(";");
await cmd.ExecuteAsync(default);
var error = await new StreamReader(cmd.ExtendedOutputStream).ReadToEndAsync();
if (!string.IsNullOrEmpty(error))
{
Console.WriteLine(error);
}

client.Disconnect();

#endregion

Assert.Inconclusive();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there something to discuss here? Console.WriteLine and Inconclusive...

}
}
#endif

/// <summary>
///A test for BeginExecute
Expand Down
6 changes: 3 additions & 3 deletions src/Renci.SshNet.Tests/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ D8DHbFwAT2mUv1QxRXYJO1y4pENboEzT6LUqxJgE+ae/F/29g2RD9DhtwqKqWjhM
-----END DSA PRIVATE KEY-----</value>
</data>
<data name="HOST" xml:space="preserve">
<value>192.168.10.192</value>
<value>127.0.0.1</value>
</data>
<data name="INVALID_KEY" xml:space="preserve">
<value>-----BEGIN DSA PRIVATE KEY-----
Expand All @@ -166,7 +166,7 @@ tM7dZpB+reWl9L5e2L8=
-----END DSA PRIVATE KEY-----</value>
</data>
<data name="PASSWORD" xml:space="preserve">
<value>tester</value>
<value>jasmine1015</value>
</data>
<data name="PORT" xml:space="preserve">
<value>22</value>
Expand Down Expand Up @@ -239,6 +239,6 @@ Vakr7Sa3K5niCyH5kxdyO1t29l1ksBqpDUrj+vViFuLkd3XIiui8IA==
-----END RSA PRIVATE KEY-----</value>
</data>
<data name="USERNAME" xml:space="preserve">
<value>tester</value>
<value>kendallb</value>
</data>
</root>
4 changes: 1 addition & 3 deletions src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@
<LangVersion>7.3</LangVersion>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>..\Renci.SshNet.snk</AssemblyOriginatorKeyFile>
<TargetFramework>net472</TargetFramework>
</PropertyGroup>

<PropertyGroup Condition=" '$(VisualStudioVersion)' == '15.0' ">
<TargetFrameworks>net35;net472;netcoreapp2.1</TargetFrameworks>
</PropertyGroup>
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '16.0' ">
<TargetFrameworks>net35;net472;netcoreapp3.1;net5.0</TargetFrameworks>
</PropertyGroup>
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '17.0' ">
<TargetFrameworks>net472;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
</PropertyGroup>
Expand Down
3 changes: 3 additions & 0 deletions src/Renci.SshNet.VS2019.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/Environment/Hierarchy/Build/BuildTool/CustomBuildToolPath/@EntryValue">C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\MSBuild.exe</s:String>
<s:Int64 x:Key="/Default/Environment/Hierarchy/Build/BuildTool/MsbuildVersion/@EntryValue">1048576</s:Int64></wpf:ResourceDictionary>
11 changes: 4 additions & 7 deletions src/Renci.SshNet/Channels/Channel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -562,14 +562,11 @@ protected virtual void Close()
// this also ensures don't raise the Closed event more than once
IsOpen = false;

if (_closeMessageReceived)
// raise event signaling that both ends of the channel have been closed
var closed = Closed;
if (closed != null)
{
// raise event signaling that both ends of the channel have been closed
var closed = Closed;
if (closed != null)
{
closed(this, new ChannelEventArgs(LocalChannelNumber));
}
closed(this, new ChannelEventArgs(LocalChannelNumber));
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Renci.SshNet/Renci.SshNet.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<AssemblyOriginatorKeyFile>../Renci.SshNet.snk</AssemblyOriginatorKeyFile>
<LangVersion>6</LangVersion>
<SignAssembly>true</SignAssembly>
<TargetFrameworks>net35;net40;net472;netstandard1.3;netstandard2.0</TargetFrameworks>
<TargetFramework>net472</TargetFramework>
</PropertyGroup>

<!--
Expand Down
29 changes: 29 additions & 0 deletions src/Renci.SshNet/SshClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
using System.Text;
using System.Diagnostics.CodeAnalysis;
using System.Net;
#if FEATURE_TAP
using System.Threading;
using System.Threading.Tasks;
#endif
using Renci.SshNet.Common;

namespace Renci.SshNet
Expand Down Expand Up @@ -275,6 +279,31 @@ public SshCommand RunCommand(string commandText)
return cmd;
}

#if FEATURE_TAP
/// <summary>
/// Creates and executes the command.
/// </summary>
/// <param name="commandText">The command text.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to observe.</param>
/// <returns>Returns an instance of <see cref="Task{SshCommand}"/> with execution results.</returns>
/// <remarks>This method internally uses asynchronous calls.</remarks>
/// <example>
/// <code source="..\..\src\Renci.SshNet.Tests\Classes\SshCommandTest.cs" region="Example SshCommand RunCommand Result Async" language="C#" title="Running simple command async" />
/// <code source="..\..\src\Renci.SshNet.Tests\Classes\SshCommandTest.cs" region="Example SshCommand RunCommand Result Async With Short Timeout" language="C#" title="Running simple command with short timeout async" />
/// </example>
/// <exception cref="ArgumentException">CommandText property is empty.</exception>
/// <exception cref="T:Renci.SshNet.Common.SshException">Invalid Operation - An existing channel was used to execute this command.</exception>
/// <exception cref="InvalidOperationException">Asynchronous operation is already in progress.</exception>
/// <exception cref="SshConnectionException">Client is not connected.</exception>
/// <exception cref="ArgumentNullException"><paramref name="commandText"/> is <c>null</c>.</exception>
public async Task<SshCommand> RunCommandAsync(string commandText, CancellationToken cancellationToken)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The more I think about it I don't feel we should have such simple helpers... yes, this is aligned with the sync version, but... something to discuss with Gert, I think.

{
var cmd = CreateCommand(commandText);
await cmd.ExecuteAsync(cancellationToken).ConfigureAwait(false);
return cmd;
}
#endif

/// <summary>
/// Creates the shell.
/// </summary>
Expand Down
Loading