From 7ebad663481cafceaefe405578fac4816aef54b5 Mon Sep 17 00:00:00 2001
From: Aleksei Slaikovskii <aslaikov@redhat.com>
Date: Dec 15 2017 10:26:17 +0000
Subject: Complete fix for OpenSSH transport and freeze fix for Paramiko


This patch fixes OpenSSH transport. Also, it happened it should
fix tests execution freezes when stdin doesn't get closed
while using Paramiko transport.

---

diff --git a/Makefile b/Makefile
index 3735918..0405338 100644
--- a/Makefile
+++ b/Makefile
@@ -49,6 +49,13 @@ srpm: tarball
 	cp ${TARBALLNAME} rpmbuild/SOURCES/
 	rpmbuild --define "_topdir ${PWD}/rpmbuild" -bs ${FEDORA_PROJECT}.spec
 
+rpm: tarball
+	rm -rvf rpmbuild
+	mkdir -p rpmbuild/SOURCES
+	mkdir -p rpmbuild/SRPMS
+	cp ${TARBALLNAME} rpmbuild/SOURCES/
+	rpmbuild --define "_topdir ${PWD}/rpmbuild" -bb ${FEDORA_PROJECT}.spec
+
 mock: srpm
 	cp $(TARBALLNAME) $$(rpm -E '%{_topdir}')/SOURCES
 	mock rebuild rpmbuild/SRPMS/*.src.rpm
diff --git a/pytest_multihost/host.py b/pytest_multihost/host.py
index a095a43..9d035c5 100644
--- a/pytest_multihost/host.py
+++ b/pytest_multihost/host.py
@@ -233,6 +233,11 @@ class BaseHost(object):
         if self.command_prelude:
             command.stdin.write(self.command_prelude)
 
+        if stdin_text:
+            command.stdin.write('echo -e "')
+            command.stdin.write(stdin_text.replace('"', '\\\"'))
+            command.stdin.write('" | ')
+
         if isinstance(argv, basestring):
             # Run a shell command given as a string
             command.stdin.write('(')
@@ -244,16 +249,13 @@ class BaseHost(object):
                 command.stdin.write(shell_quote(arg))
                 command.stdin.write(' ')
 
-        command.stdin.write(';exit\n')
-        if stdin_text:
-            command.stdin.write(stdin_text)
+        command.stdin.write('\nexit\n')
         command.stdin.flush()
         if not bg:
             command.wait(raiseonerr=raiseonerr)
         return command
 
 
-
 class Host(BaseHost):
     """A Unix host"""
     command_prelude = 'set -e\n'
diff --git a/pytest_multihost/transport.py b/pytest_multihost/transport.py
index b8984e3..d5ba7ac 100644
--- a/pytest_multihost/transport.py
+++ b/pytest_multihost/transport.py
@@ -11,6 +11,7 @@ OpenSSHTransport (if Paramiko is not importable, or the
 PYTESTMULTIHOST_SSH_TRANSPORT environment variable is set to "openssh").
 """
 
+import base64
 import os
 import socket
 import threading
@@ -364,10 +365,10 @@ class OpenSSHTransport(Transport):
 
     def get_file_contents(self, filename, encoding=None):
         self.log.info('GET %s', filename)
-        cmd = self._run(['cat', filename], log_stdout=False)
+        cmd = self._run(['base64', filename], log_stdout=False)
         cmd.wait(raiseonerr=False)
         if cmd.returncode == 0:
-            result = cmd.stdout_text
+            result = base64.b64decode(cmd.stdout_text)
             if encoding:
                 result = result.decode(encoding)
             return result
diff --git a/test_pytestmultihost/test_localhost.py b/test_pytestmultihost/test_localhost.py
index 180aa73..5edfeff 100644
--- a/test_pytestmultihost/test_localhost.py
+++ b/test_pytestmultihost/test_localhost.py
@@ -18,6 +18,7 @@ except ImportError:
     class AuthenticationException(Exception):
         """Never raised"""
 
+
 def get_conf_dict():
     return {
         'ssh_username': getpass.getuser(),
@@ -51,6 +52,7 @@ def get_conf_dict():
         ],
     }
 
+
 @pytest.fixture(scope='class', params=['paramiko', 'openssh'])
 def transport_class(request):
     if request.param == 'paramiko':
@@ -60,6 +62,7 @@ def transport_class(request):
     else:
         raise ValueError('bad transport_class')
 
+
 @pytest.fixture(scope='class')
 def multihost(request, transport_class):
     conf = get_conf_dict()
@@ -80,6 +83,7 @@ def multihost(request, transport_class):
     assert isinstance(mh.host.transport, transport_class)
     return mh.install()
 
+
 @pytest.fixture(scope='class')
 def multihost_baduser(request, transport_class):
     conf = get_conf_dict()
@@ -98,6 +102,7 @@ def multihost_baduser(request, transport_class):
     mh.host.transport_class = transport_class
     return mh.install()
 
+
 @pytest.fixture(scope='class')
 def multihost_badpassword(request, transport_class):
     conf = get_conf_dict()
@@ -123,7 +128,7 @@ def _first_command(host):
     try:
         yield
     except (AuthenticationException, CalledProcessError):
-        print (
+        print(
             'Cannot login to %s using default SSH key (%s), user %s. '
             'You might want to add your own key '
             'to ~/.ssh/authorized_keys.'
@@ -140,7 +145,7 @@ class TestLocalhost(object):
         host = multihost.host
         with _first_command(host):
             echo = host.run_command(['echo', 'hello', 'world'])
-        assert echo.stdout_text == 'hello world\n'
+        assert 'hello world\n' in echo.stdout_text
 
     def test_put_get_file_contents(self, multihost, tmpdir):
         host = multihost.host
@@ -196,39 +201,61 @@ class TestLocalhost(object):
             host.transport.rmdir(filename)
         assert not os.path.exists(filename)
 
+    def test_background(self, multihost):
+        host = multihost.host
+
+        host.run_command("echo bla > /tmp/testfile.in")
+
+        run_nc = 'nc -k -l 12080 > /tmp/filename.out'
+        host.run_command(run_nc, bg=True, raiseonerr=False)
+
+        send_file = (
+            'if [[ $(ps aux | grep "nc -l 18999" | grep -v grep | '
+            'wc -l) -eq 1 ]]; then echo "bla" ; else sleep 2; fi ; '
+            'nc localhost 12080 < /tmp/testfile.in'
+        )
+        client = host.run_command(send_file)
+        assert client.returncode == 0
+
+    def test_escaping(self, multihost):
+        host = multihost.host
+        test_file_path = "/tmp/filename.in"
+
+        stdin_text = '"test", test, "test"'
+        tee = host.run_command(
+            ["tee", test_file_path],
+            stdin_text=stdin_text
+        )
+        with open(test_file_path, "r") as f:
+            assert f.read() == tee.stdout_text
+
     def test_reset(self, multihost):
         host = multihost.host
         with _first_command(host):
             echo = host.run_command(['echo', 'hello', 'world'])
-        assert echo.stdout_text == 'hello world\n'
+        assert 'hello world\n' in echo.stdout_text
 
         host.ssh_password = 'BAD PASSWORD'
         host.ssh_key_filename = None
         echo = host.run_command(['echo', 'hello', 'world'])
-        assert echo.stdout_text == 'hello world\n'
+        assert 'hello world\n' in echo.stdout_text
 
         host.reset_connection()
+        host.ssh_password = None
         with pytest.raises((AuthenticationException, RuntimeError)):
             echo = host.run_command(['echo', 'hello', 'world'])
 
-
     def test_baduser(self, multihost_baduser, tmpdir):
         host = multihost_baduser.host
         if host.transport_class == pytest_multihost.transport.OpenSSHTransport:
             # Avoid the OpenSSH password prompt
             return
         with pytest.raises(AuthenticationException):
-            echo = host.run_command(['echo', 'hello', 'world'])
+            host.run_command(['echo', 'hello', 'world'])
 
     def test_badpassword(self, multihost_badpassword, tmpdir):
         host = multihost_badpassword.host
         with pytest.raises((AuthenticationException, RuntimeError)):
-            echo = host.run_command(['echo', 'hello', 'world'])
+            host.run_command(['echo', 'hello', 'world'])
+
 
-    def test_background(self, multihost):
-	host = multihost.host
-	run_nc = 'nc -l 12080 > /tmp/filename.out'
-	cmd = host.run_command(run_nc, bg=True, raiseonerr=False)
-	send_file = 'nc localhost 12080 < /root/anaconda-ks.cfg'
-	cmd = host.run_command(send_file)
-	assert cmd.returncode == 0

