浏览代码

Reintroduce internal Channel.SendEof method.

Gert Driesen 11 年之前
父节点
当前提交
d9b9e035ca
共有 13 个文件被更改,包括 414 次插入56 次删除
  1. 10 7
      Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_Close_SessionIsConnectedAndChannelIsNotOpen.cs
  2. 10 7
      Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_Close_SessionIsConnectedAndChannelIsOpen_EofNotReceived.cs
  3. 137 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_Close_SessionIsConnectedAndChannelIsOpen_EofNotReceived_SendEofInvoked.cs
  4. 10 7
      Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_Close_SessionIsConnectedAndChannelIsOpen_EofReceived.cs
  5. 10 7
      Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_Close_SessionIsNotConnectedAndChannelIsNotOpen.cs
  6. 10 7
      Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_Close_SessionIsNotConnectedAndChannelIsOpen.cs
  7. 10 7
      Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionChannelCloseReceived_SessionIsConnectedAndChannelIsOpen_EofNotReceived.cs
  8. 10 7
      Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionChannelCloseReceived_SessionIsConnectedAndChannelIsOpen_EofReceived.cs
  9. 10 7
      Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionDisconnected_SessionIsConnectedAndChannelIsOpen.cs
  10. 91 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_SendEof_ChannelIsNotOpen.cs
  11. 85 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_SendEof_ChannelIsOpen.cs
  12. 3 0
      Renci.SshClient/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj
  13. 18 0
      Renci.SshClient/Renci.SshNet/Channels/Channel.cs

+ 10 - 7
Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_Close_SessionIsConnectedAndChannelIsNotOpen.cs

@@ -17,6 +17,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
         private uint _localChannelNumber;
         private Channel _channel;
         private List<ChannelEventArgs> _channelClosedRegister;
+        private IList<ExceptionEventArgs> _channelExceptionRegister;
 
         [TestInitialize]
         public void Initialize()
@@ -32,19 +33,15 @@ namespace Renci.SshNet.Tests.Classes.Channels
             _localWindowSize = (uint)random.Next(0, int.MaxValue);
             _localPacketSize = (uint)random.Next(0, int.MaxValue);
             _channelClosedRegister = new List<ChannelEventArgs>();
+            _channelExceptionRegister = new List<ExceptionEventArgs>();
 
             _sessionMock = new Mock<ISession>(MockBehavior.Strict);
 
             _sessionMock.Setup(p => p.IsConnected).Returns(true);
 
             _channel = new ChannelStub(_sessionMock.Object, _localChannelNumber, _localWindowSize, _localPacketSize);
-            _channel.Closed += (sender, args) =>
-                {
-                    lock (this)
-                    {
-                        _channelClosedRegister.Add(args);
-                    }
-                };
+            _channel.Closed += (sender, args) => _channelClosedRegister.Add(args);
+            _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
         }
 
         private void Act()
@@ -69,5 +66,11 @@ namespace Renci.SshNet.Tests.Classes.Channels
         {
             Assert.AreEqual(0, _channelClosedRegister.Count);
         }
+
+        [TestMethod]
+        public void ExceptionShouldNeverHaveFired()
+        {
+            Assert.AreEqual(0, _channelExceptionRegister.Count);
+        }
     }
 }

+ 10 - 7
Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_Close_SessionIsConnectedAndChannelIsOpen_EofNotReceived.cs

@@ -22,6 +22,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
         private ChannelStub _channel;
         private Stopwatch _closeTimer;
         private List<ChannelEventArgs> _channelClosedRegister;
+        private IList<ExceptionEventArgs> _channelExceptionRegister;
 
         [TestInitialize]
         public void Initialize()
@@ -41,6 +42,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
             _remotePacketSize = (uint)random.Next(0, int.MaxValue);
             _closeTimer = new Stopwatch();
             _channelClosedRegister = new List<ChannelEventArgs>();
+            _channelExceptionRegister = new List<ExceptionEventArgs>();
 
             _sessionMock = new Mock<ISession>(MockBehavior.Strict);
 
@@ -74,13 +76,8 @@ namespace Renci.SshNet.Tests.Classes.Channels
                     });
 
             _channel = new ChannelStub(_sessionMock.Object, _localChannelNumber, _localWindowSize, _localPacketSize);
-            _channel.Closed += (sender, args) =>
-                {
-                    lock (this)
-                    {
-                        _channelClosedRegister.Add(args);
-                    }
-                };
+            _channel.Closed += (sender, args) => _channelClosedRegister.Add(args);
+            _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
             _channel.InitializeRemoteChannelInfo(_remoteChannelNumber, _remoteWindowSize, _remotePacketSize);
             _channel.SetIsOpen(true);
         }
@@ -130,5 +127,11 @@ namespace Renci.SshNet.Tests.Classes.Channels
             Assert.AreEqual(1, _channelClosedRegister.Count);
             Assert.AreEqual(_localChannelNumber, _channelClosedRegister[0].ChannelNumber);
         }
+
+        [TestMethod]
+        public void ExceptionShouldNeverHaveFired()
+        {
+            Assert.AreEqual(0, _channelExceptionRegister.Count);
+        }
     }
 }

+ 137 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_Close_SessionIsConnectedAndChannelIsOpen_EofNotReceived_SendEofInvoked.cs

@@ -0,0 +1,137 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Threading;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+
+namespace Renci.SshNet.Tests.Classes.Channels
+{
+    [TestClass]
+    public class ChannelTest_Close_SessionIsConnectedAndChannelIsOpen_EofNotReceived_SendEofInvoked
+    {
+        private Mock<ISession> _sessionMock;
+        private uint _localChannelNumber;
+        private uint _localWindowSize;
+        private uint _localPacketSize;
+        private uint _remoteChannelNumber;
+        private uint _remoteWindowSize;
+        private uint _remotePacketSize;
+        private ChannelStub _channel;
+        private Stopwatch _closeTimer;
+        private List<ChannelEventArgs> _channelClosedRegister;
+        private IList<ExceptionEventArgs> _channelExceptionRegister;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void Arrange()
+        {
+            var random = new Random();
+            _localChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _localWindowSize = (uint)random.Next(0, int.MaxValue);
+            _localPacketSize = (uint)random.Next(0, int.MaxValue);
+            _remoteChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _remoteWindowSize = (uint)random.Next(0, int.MaxValue);
+            _remotePacketSize = (uint)random.Next(0, int.MaxValue);
+            _closeTimer = new Stopwatch();
+            _channelClosedRegister = new List<ChannelEventArgs>();
+            _channelExceptionRegister = new List<ExceptionEventArgs>();
+
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+
+            var sequence = new MockSequence();
+            _sessionMock.InSequence(sequence).Setup(p => p.SendMessage(It.Is<ChannelEofMessage>(c => c.LocalChannelNumber == _remoteChannelNumber)));
+            _sessionMock.InSequence(sequence).Setup(p => p.IsConnected).Returns(true);
+            _sessionMock.InSequence(sequence).Setup(p => p.TrySendMessage(It.Is<ChannelCloseMessage>(c => c.LocalChannelNumber == _remoteChannelNumber))).Returns(true);
+            _sessionMock.InSequence(sequence).Setup(p => p.WaitOnHandle(It.IsAny<EventWaitHandle>()))
+                .Callback<WaitHandle>(w =>
+                {
+                    new Thread(() =>
+                    {
+                        Thread.Sleep(100);
+                        // raise ChannelCloseReceived event to set waithandle for receiving
+                        // SSH_MSG_CHANNEL_CLOSE message from server which is waited on after
+                        // sending the SSH_MSG_CHANNEL_CLOSE message to the server
+                        _sessionMock.Raise(s => s.ChannelCloseReceived += null,
+                            new MessageEventArgs<ChannelCloseMessage>(
+                                new ChannelCloseMessage(_localChannelNumber)));
+                    }).Start();
+                    _closeTimer.Start();
+                    try
+                    {
+                        w.WaitOne();
+                    }
+                    finally
+                    {
+                        _closeTimer.Stop();
+                    }
+                });
+
+            _channel = new ChannelStub(_sessionMock.Object, _localChannelNumber, _localWindowSize, _localPacketSize);
+            _channel.Closed += (sender, args) => _channelClosedRegister.Add(args);
+            _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
+            _channel.InitializeRemoteChannelInfo(_remoteChannelNumber, _remoteWindowSize, _remotePacketSize);
+            _channel.SetIsOpen(true);
+            _channel.SendEof();
+        }
+
+        private void Act()
+        {
+            _channel.Close();
+        }
+
+        [TestMethod]
+        public void IsOpenShouldReturnFalse()
+        {
+            Assert.IsFalse(_channel.IsOpen);
+        }
+
+        [TestMethod]
+        public void TrySendMessageOnSessionShouldBeInvokedOnceForChannelCloseMessage()
+        {
+            _sessionMock.Verify(
+                p => p.TrySendMessage(It.Is<ChannelCloseMessage>(c => c.LocalChannelNumber == _remoteChannelNumber)),
+                Times.Once);
+        }
+
+        [TestMethod]
+        public void SendMessageOnSessionShouldBeInvokedOnceForChannelEofMessage()
+        {
+            _sessionMock.Verify(
+                p => p.SendMessage(It.Is<ChannelEofMessage>(c => c.LocalChannelNumber == _remoteChannelNumber)),
+                Times.Once);
+        }
+
+        [TestMethod]
+        public void WaitOnHandleOnSessionShouldBeInvokedOnce()
+        {
+            _sessionMock.Verify(p => p.WaitOnHandle(It.IsAny<EventWaitHandle>()), Times.Once);
+        }
+
+        [TestMethod]
+        public void WaitOnHandleOnSessionShouldWaitForChannelCloseMessageToBeReceived()
+        {
+            Assert.IsTrue(_closeTimer.ElapsedMilliseconds >= 100, "Elapsed milliseconds=" + _closeTimer.ElapsedMilliseconds);
+        }
+
+        [TestMethod]
+        public void ClosedEventShouldHaveFiredOnce()
+        {
+            Assert.AreEqual(1, _channelClosedRegister.Count);
+            Assert.AreEqual(_localChannelNumber, _channelClosedRegister[0].ChannelNumber);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNeverHaveFired()
+        {
+            Assert.AreEqual(0, _channelExceptionRegister.Count);
+        }
+    }
+}

+ 10 - 7
Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_Close_SessionIsConnectedAndChannelIsOpen_EofReceived.cs

@@ -22,6 +22,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
         private ChannelStub _channel;
         private Stopwatch _closeTimer;
         private List<ChannelEventArgs> _channelClosedRegister;
+        private IList<ExceptionEventArgs> _channelExceptionRegister;
 
         [TestInitialize]
         public void Initialize()
@@ -41,6 +42,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
             _remotePacketSize = (uint)random.Next(0, int.MaxValue);
             _closeTimer = new Stopwatch();
             _channelClosedRegister = new List<ChannelEventArgs>();
+            _channelExceptionRegister = new List<ExceptionEventArgs>();
 
             _sessionMock = new Mock<ISession>(MockBehavior.Strict);
 
@@ -72,13 +74,8 @@ namespace Renci.SshNet.Tests.Classes.Channels
                     });
 
             _channel = new ChannelStub(_sessionMock.Object, _localChannelNumber, _localWindowSize, _localPacketSize);
-            _channel.Closed += (sender, args) =>
-                {
-                    lock (this)
-                    {
-                        _channelClosedRegister.Add(args);
-                    }
-                };
+            _channel.Closed += (sender, args) => _channelClosedRegister.Add(args);
+            _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
             _channel.InitializeRemoteChannelInfo(_remoteChannelNumber, _remoteWindowSize, _remotePacketSize);
             _channel.SetIsOpen(true);
 
@@ -132,5 +129,11 @@ namespace Renci.SshNet.Tests.Classes.Channels
             Assert.AreEqual(1, _channelClosedRegister.Count);
             Assert.AreEqual(_localChannelNumber, _channelClosedRegister[0].ChannelNumber);
         }
+
+        [TestMethod]
+        public void ExceptionShouldNeverHaveFired()
+        {
+            Assert.AreEqual(0, _channelExceptionRegister.Count);
+        }
     }
 }

+ 10 - 7
Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_Close_SessionIsNotConnectedAndChannelIsNotOpen.cs

@@ -17,6 +17,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
         private uint _localChannelNumber;
         private Channel _channel;
         private List<ChannelEventArgs> _channelClosedRegister;
+        private IList<ExceptionEventArgs> _channelExceptionRegister;
 
         [TestInitialize]
         public void Initialize()
@@ -32,19 +33,15 @@ namespace Renci.SshNet.Tests.Classes.Channels
             _localWindowSize = (uint) random.Next(0, int.MaxValue);
             _localPacketSize = (uint) random.Next(0, int.MaxValue);
             _channelClosedRegister = new List<ChannelEventArgs>();
+            _channelExceptionRegister = new List<ExceptionEventArgs>();
 
             _sessionMock = new Mock<ISession>(MockBehavior.Strict);
 
             _sessionMock.Setup(p => p.IsConnected).Returns(false);
 
             _channel = new ChannelStub(_sessionMock.Object, _localChannelNumber, _localWindowSize, _localPacketSize);
-            _channel.Closed += (sender, args) =>
-                {
-                    lock (this)
-                    {
-                        _channelClosedRegister.Add(args);
-                    }
-                };
+            _channel.Closed += (sender, args) => _channelClosedRegister.Add(args);
+            _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
         }
 
         private void Act()
@@ -69,5 +66,11 @@ namespace Renci.SshNet.Tests.Classes.Channels
         {
             Assert.AreEqual(0, _channelClosedRegister.Count);
         }
+
+        [TestMethod]
+        public void ExceptionShouldNeverHaveFired()
+        {
+            Assert.AreEqual(0, _channelExceptionRegister.Count);
+        }
     }
 }

+ 10 - 7
Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_Close_SessionIsNotConnectedAndChannelIsOpen.cs

@@ -16,6 +16,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
         private uint _localChannelNumber;
         private ChannelStub _channel;
         private List<ChannelEventArgs> _channelClosedRegister;
+        private IList<ExceptionEventArgs> _channelExceptionRegister;
 
         [TestInitialize]
         public void Initialize()
@@ -31,19 +32,15 @@ namespace Renci.SshNet.Tests.Classes.Channels
             _localWindowSize = (uint)random.Next(0, int.MaxValue);
             _localPacketSize = (uint)random.Next(0, int.MaxValue);
             _channelClosedRegister = new List<ChannelEventArgs>();
+            _channelExceptionRegister = new List<ExceptionEventArgs>();
 
             _sessionMock = new Mock<ISession>(MockBehavior.Strict);
 
             _sessionMock.Setup(p => p.IsConnected).Returns(false);
 
             _channel = new ChannelStub(_sessionMock.Object, _localChannelNumber, _localWindowSize, _localPacketSize);
-            _channel.Closed += (sender, args) =>
-                {
-                    lock (this)
-                    {
-                        _channelClosedRegister.Add(args);
-                    }
-                };
+            _channel.Closed += (sender, args) => _channelClosedRegister.Add(args);
+            _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
             _channel.SetIsOpen(true);
         }
 
@@ -69,5 +66,11 @@ namespace Renci.SshNet.Tests.Classes.Channels
         {
             Assert.AreEqual(0, _channelClosedRegister.Count);
         }
+
+        [TestMethod]
+        public void ExceptionShouldNeverHaveFired()
+        {
+            Assert.AreEqual(0, _channelExceptionRegister.Count);
+        }
     }
 }

+ 10 - 7
Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionChannelCloseReceived_SessionIsConnectedAndChannelIsOpen_EofNotReceived.cs

@@ -19,6 +19,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
         private uint _remoteWindowSize;
         private uint _remotePacketSize;
         private IList<ChannelEventArgs> _channelClosedRegister;
+        private IList<ExceptionEventArgs> _channelExceptionRegister;
         private ChannelStub _channel;
 
         [TestInitialize]
@@ -38,6 +39,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
             _remoteWindowSize = (uint)random.Next(0, int.MaxValue);
             _remotePacketSize = (uint)random.Next(0, int.MaxValue);
             _channelClosedRegister = new List<ChannelEventArgs>();
+            _channelExceptionRegister = new List<ExceptionEventArgs>();
 
             _sessionMock = new Mock<ISession>(MockBehavior.Strict);
 
@@ -45,13 +47,8 @@ namespace Renci.SshNet.Tests.Classes.Channels
             _sessionMock.Setup(p => p.TrySendMessage(It.Is<ChannelCloseMessage>(c => c.LocalChannelNumber == _remoteChannelNumber))).Returns(true);
 
             _channel = new ChannelStub(_sessionMock.Object, _localChannelNumber, _localWindowSize, _localPacketSize);
-            _channel.Closed += (sender, args) =>
-                {
-                    lock (this)
-                    {
-                        _channelClosedRegister.Add(args);
-                    }
-                };
+            _channel.Closed += (sender, args) => _channelClosedRegister.Add(args);
+            _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
             _channel.InitializeRemoteChannelInfo(_remoteChannelNumber, _remoteWindowSize, _remotePacketSize);
             _channel.SetIsOpen(true);
         }
@@ -96,5 +93,11 @@ namespace Renci.SshNet.Tests.Classes.Channels
             Assert.AreEqual(1, _channelClosedRegister.Count);
             Assert.AreEqual(_localChannelNumber, _channelClosedRegister[0].ChannelNumber);
         }
+
+        [TestMethod]
+        public void ExceptionShouldNeverHaveFired()
+        {
+            Assert.AreEqual(0, _channelExceptionRegister.Count);
+        }
     }
 }

+ 10 - 7
Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionChannelCloseReceived_SessionIsConnectedAndChannelIsOpen_EofReceived.cs

@@ -19,6 +19,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
         private uint _remoteWindowSize;
         private uint _remotePacketSize;
         private IList<ChannelEventArgs> _channelClosedRegister;
+        private IList<ExceptionEventArgs> _channelExceptionRegister;
         private ChannelStub _channel;
 
         [TestInitialize]
@@ -38,6 +39,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
             _remoteWindowSize = (uint)random.Next(0, int.MaxValue);
             _remotePacketSize = (uint)random.Next(0, int.MaxValue);
             _channelClosedRegister = new List<ChannelEventArgs>();
+            _channelExceptionRegister = new List<ExceptionEventArgs>();
 
             _sessionMock = new Mock<ISession>(MockBehavior.Strict);
 
@@ -47,13 +49,8 @@ namespace Renci.SshNet.Tests.Classes.Channels
                 .Returns(true);
 
             _channel = new ChannelStub(_sessionMock.Object, _localChannelNumber, _localWindowSize, _localPacketSize);
-            _channel.Closed += (sender, args) =>
-                {
-                    lock (this)
-                    {
-                        _channelClosedRegister.Add(args);
-                    }
-                };
+            _channel.Closed += (sender, args) => _channelClosedRegister.Add(args);
+            _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
             _channel.InitializeRemoteChannelInfo(_remoteChannelNumber, _remoteWindowSize, _remotePacketSize);
             _channel.SetIsOpen(true);
 
@@ -101,5 +98,11 @@ namespace Renci.SshNet.Tests.Classes.Channels
             Assert.AreEqual(1, _channelClosedRegister.Count);
             Assert.AreEqual(_localChannelNumber, _channelClosedRegister[0].ChannelNumber);
         }
+
+        [TestMethod]
+        public void ExceptionShouldNeverHaveFired()
+        {
+            Assert.AreEqual(0, _channelExceptionRegister.Count);
+        }
     }
 }

+ 10 - 7
Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionDisconnected_SessionIsConnectedAndChannelIsOpen.cs

@@ -18,6 +18,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
         private uint _remotePacketSize;
         private ChannelStub _channel;
         private List<ChannelEventArgs> _channelClosedRegister;
+        private IList<ExceptionEventArgs> _channelExceptionRegister;
 
         [TestInitialize]
         public void Initialize()
@@ -36,19 +37,15 @@ namespace Renci.SshNet.Tests.Classes.Channels
             _remoteWindowSize = (uint)random.Next(0, int.MaxValue);
             _remotePacketSize = (uint)random.Next(0, int.MaxValue);
             _channelClosedRegister = new List<ChannelEventArgs>();
+            _channelExceptionRegister = new List<ExceptionEventArgs>();
 
             _sessionMock = new Mock<ISession>(MockBehavior.Strict);
 
             _sessionMock.Setup(p => p.IsConnected).Returns(true);
 
             _channel = new ChannelStub(_sessionMock.Object, _localChannelNumber, _localWindowSize, _localPacketSize);
-            _channel.Closed += (sender, args) =>
-                {
-                    lock (this)
-                    {
-                        _channelClosedRegister.Add(args);
-                    }
-                };
+            _channel.Closed += (sender, args) => _channelClosedRegister.Add(args);
+            _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
             _channel.InitializeRemoteChannelInfo(_remoteChannelNumber, _remoteWindowSize, _remotePacketSize);
             _channel.SetIsOpen(true);
         }
@@ -69,5 +66,11 @@ namespace Renci.SshNet.Tests.Classes.Channels
         {
             Assert.AreEqual(0, _channelClosedRegister.Count);
         }
+
+        [TestMethod]
+        public void ExceptionShouldNeverHaveFired()
+        {
+            Assert.AreEqual(0, _channelExceptionRegister.Count);
+        }
     }
 }

+ 91 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_SendEof_ChannelIsNotOpen.cs

@@ -0,0 +1,91 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages;
+
+namespace Renci.SshNet.Tests.Classes.Channels
+{
+    [TestClass]
+    public class ChannelTest_SendEof_ChannelIsNotOpen
+    {
+        private Mock<ISession> _sessionMock;
+        private uint _localWindowSize;
+        private uint _localPacketSize;
+        private uint _localChannelNumber;
+        private ChannelStub _channel;
+        private List<ChannelEventArgs> _channelClosedRegister;
+        private IList<ExceptionEventArgs> _channelExceptionRegister;
+        private InvalidOperationException _actualException;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void Arrange()
+        {
+            var random = new Random();
+            _localChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _localWindowSize = (uint)random.Next(0, int.MaxValue);
+            _localPacketSize = (uint)random.Next(0, int.MaxValue);
+            _channelClosedRegister = new List<ChannelEventArgs>();
+            _channelExceptionRegister = new List<ExceptionEventArgs>();
+            _actualException = null;
+
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+
+            _channel = new ChannelStub(_sessionMock.Object, _localChannelNumber, _localWindowSize, _localPacketSize);
+            _channel.Closed += (sender, args) => _channelClosedRegister.Add(args);
+            _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
+        }
+
+        private void Act()
+        {
+            try
+            {
+                _channel.SendEof();
+                Assert.Fail();
+            }
+            catch (InvalidOperationException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void IsOpenShouldReturnFalse()
+        {
+            Assert.IsFalse(_channel.IsOpen);
+        }
+
+        [TestMethod]
+        public void SendEofShouldHaveThrownInvalidOperationException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual("The channel is closed.", _actualException.Message);
+        }
+
+        [TestMethod]
+        public void SendMessageOnSessionShouldNeverBeInvoked()
+        {
+            _sessionMock.Verify(p => p.SendMessage(It.IsAny<Message>()), Times.Never);
+        }
+
+        [TestMethod]
+        public void ClosedEventShouldNeverHaveFired()
+        {
+            Assert.AreEqual(0, _channelClosedRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNeverHaveFired()
+        {
+            Assert.AreEqual(0, _channelExceptionRegister.Count);
+        }
+    }
+}

+ 85 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_SendEof_ChannelIsOpen.cs

@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+
+namespace Renci.SshNet.Tests.Classes.Channels
+{
+    [TestClass]
+    public class ChannelTest_SendEof_ChannelIsOpen
+    {
+        private Mock<ISession> _sessionMock;
+        private uint _localChannelNumber;
+        private uint _localWindowSize;
+        private uint _localPacketSize;
+        private uint _remoteChannelNumber;
+        private uint _remoteWindowSize;
+        private uint _remotePacketSize;
+        private ChannelStub _channel;
+        private List<ChannelEventArgs> _channelClosedRegister;
+        private IList<ExceptionEventArgs> _channelExceptionRegister;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void Arrange()
+        {
+            var random = new Random();
+            _localChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _localWindowSize = (uint)random.Next(0, int.MaxValue);
+            _localPacketSize = (uint)random.Next(0, int.MaxValue);
+            _remoteChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _remoteWindowSize = (uint)random.Next(0, int.MaxValue);
+            _remotePacketSize = (uint)random.Next(0, int.MaxValue);
+            _channelClosedRegister = new List<ChannelEventArgs>();
+            _channelExceptionRegister = new List<ExceptionEventArgs>();
+
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+
+            _sessionMock.Setup(p => p.SendMessage(It.Is<ChannelEofMessage>(e => e.LocalChannelNumber == _remoteChannelNumber)));
+
+            _channel = new ChannelStub(_sessionMock.Object, _localChannelNumber, _localWindowSize, _localPacketSize);
+            _channel.Closed += (sender, args) => _channelClosedRegister.Add(args);
+            _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
+            _channel.InitializeRemoteChannelInfo(_remoteChannelNumber, _remoteWindowSize, _remotePacketSize);
+            _channel.SetIsOpen(true);
+        }
+
+        private void Act()
+        {
+            _channel.SendEof();
+        }
+
+        [TestMethod]
+        public void IsOpenShouldReturnTrue()
+        {
+            Assert.IsTrue(_channel.IsOpen);
+        }
+
+        [TestMethod]
+        public void SendMessageOnSessionShouldBeInvokedOnceWithChannelEofMessage()
+        {
+            _sessionMock.Verify(
+                p => p.SendMessage(It.Is<ChannelEofMessage>(e => e.LocalChannelNumber == _remoteChannelNumber)),
+                Times.Once);
+        }
+
+        [TestMethod]
+        public void ClosedEventShouldNeverHaveFired()
+        {
+            Assert.AreEqual(0, _channelClosedRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNeverHaveFired()
+        {
+            Assert.AreEqual(0, _channelExceptionRegister.Count);
+        }
+    }
+}

+ 3 - 0
Renci.SshClient/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj

@@ -86,6 +86,7 @@
     <Compile Include="Classes\Channels\ChannelSessionTest_Open_ExceptionWaitingOnOpenConfirmation.cs" />
     <Compile Include="Classes\Channels\ChannelSessionTest_Open_OnOpenFailureReceived_NoRetriesAvailable.cs" />
     <Compile Include="Classes\Channels\ChannelSessionTest_Open_OnOpenFailureReceived_RetriesAvalable.cs" />
+    <Compile Include="Classes\Channels\ChannelTest_Close_SessionIsConnectedAndChannelIsOpen_EofNotReceived_SendEofInvoked.cs" />
     <Compile Include="Classes\Channels\ChannelTest_Close_SessionIsConnectedAndChannelIsOpen_EofReceived.cs" />
     <Compile Include="Classes\Channels\ChannelTest_Close_SessionIsNotConnectedAndChannelIsOpen.cs" />
     <Compile Include="Classes\Channels\ChannelTest_OnSessionChannelCloseReceived_OnClose_Exception.cs" />
@@ -105,6 +106,8 @@
     <Compile Include="Classes\Channels\ChannelTest_OnSessionDisconnected_SessionIsConnectedAndChannelIsOpen.cs" />
     <Compile Include="Classes\Channels\ChannelStub.cs" />
     <Compile Include="Classes\Channels\ChannelTest_OnSessionErrorOccurred_OnErrorOccurred_Exception.cs" />
+    <Compile Include="Classes\Channels\ChannelTest_SendEof_ChannelIsNotOpen.cs" />
+    <Compile Include="Classes\Channels\ChannelTest_SendEof_ChannelIsOpen.cs" />
     <Compile Include="Classes\Channels\ClientChannelStub.cs" />
     <Compile Include="Classes\Channels\ClientChannelTest_OnSessionChannelOpenConfirmationReceived_OnOpenConfirmation_Exception.cs" />
     <Compile Include="Classes\Channels\ClientChannelTest_OnSessionChannelOpenFailureReceived_OnOpenFailure_Exception.cs" />

+ 18 - 0
Renci.SshClient/Renci.SshNet/Channels/Channel.cs

@@ -495,6 +495,19 @@ namespace Renci.SshNet.Channels
             }
         }
 
+        /// <summary>
+        /// Sends a SSH_MSG_CHANNEL_EOF message to the remote server.
+        /// </summary>
+        /// <exception cref="InvalidOperationException">The channel is closed.</exception>
+        internal void SendEof()
+        {
+            if (!IsOpen)
+                throw CreateChannelClosedException();
+
+            _session.SendMessage(new ChannelEofMessage(RemoteChannelNumber));
+            _eofMessageSent = Sent;
+        }
+
         /// <summary>
         /// Sends channel extended data message to the servers.
         /// </summary>
@@ -852,6 +865,11 @@ namespace Renci.SshNet.Channels
             throw new InvalidOperationException("The channel has not been opened, or the open has not yet been confirmed.");
         }
 
+        private InvalidOperationException CreateChannelClosedException()
+        {
+            throw new InvalidOperationException("The channel is closed.");
+        }
+
         #region IDisposable Members
 
         private bool _isDisposed;