From eee7b93c2d5a07d4a80f0edfd76697a6ca79b3bc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= <karel.koci@nic.cz>
Date: Mon, 25 Oct 2021 15:55:42 +0200
Subject: [PATCH] selftests/cli: test and fix txt and bin write and read
 functions

This tests txt_write, txt_read, bin_write and bin_read Shell methods.

There were also few issues with it that were discovered during the
testing. For example calling prompt twice causes match of new prompt.
The output of txt_read was incosistent with text prepresentation in
Python.

The new feature here is ability of txt_write to append instead of
overwrite.
---
 imgs/openwrt.sh             |  2 +-
 nsfarm/cli.py               | 17 ++++++-----
 selftests/cli/test_shell.py | 58 +++++++++++++++++++++++++++++--------
 3 files changed, 57 insertions(+), 20 deletions(-)

diff --git a/imgs/openwrt.sh b/imgs/openwrt.sh
index 47069a5..0d6d017 100644
--- a/imgs/openwrt.sh
+++ b/imgs/openwrt.sh
@@ -20,4 +20,4 @@ uci commit network
 # Now extend system
 wait4network
 opkg update
-opkg install bash coreutils-stty
+opkg install bash coreutils-stty coreutils-base64
diff --git a/nsfarm/cli.py b/nsfarm/cli.py
index e7eced8..14707f7 100644
--- a/nsfarm/cli.py
+++ b/nsfarm/cli.py
@@ -187,21 +187,23 @@ class Shell(Cli):
         Returns string containing text of the file
         """
         self.run(f"cat '{path}'")
-        return self.output
+        return self.output.replace("\r\n", "\n")
 
-    def txt_write(self, path, content):
+    def txt_write(self, path, content, append=False):
         """Write text file via shell.
 
         Note that parent directory has to exist and any file will be rewritten.
 
         path: path to be written to
         content: string with data to be written to text file.
+        append: append instead of just write.
         """
-        self.sendline(f"cat > '{path}'")
+        self.sendline(f"cat {'>>' if append else '>'} '{path}'")
         self.sendline(content)
         self.sendeof()
-        if self.prompt() != 0:
-            raise Exception(f"Writing file failed with exit code: {self.prompt()}")
+        exit_code = self.prompt()
+        if exit_code != 0:
+            raise Exception(f"Writing file failed with exit code: {exit_code}")
 
     def bin_read(self, path):
         """Read binary file via shell encoded in base64.
@@ -224,8 +226,9 @@ class Shell(Cli):
         self.sendline(f"base64 -d > '{path}'")
         self.sendline(base64.b64encode(content))
         self.sendeof()
-        if self.prompt() != 0:
-            raise Exception(f"Writing file failed with exit code: {self.prompt()}")
+        exit_code = self.prompt()
+        if exit_code != 0:
+            raise Exception(f"Writing file failed with exit code: {exit_code}")
 
     @property
     def output(self):
diff --git a/selftests/cli/test_shell.py b/selftests/cli/test_shell.py
index 41878f2..48e96d8 100644
--- a/selftests/cli/test_shell.py
+++ b/selftests/cli/test_shell.py
@@ -3,31 +3,66 @@
 import pytest
 from nsfarm.cli import Shell
 from nsfarm.lxd import Container
-# pylint: disable=no-self-use
 
 
 class Common:
-    """Common tests implementation for Shell.
-    """
+    """Common tests implementation for Shell."""
 
-    @pytest.fixture(scope="function", autouse=True)
+    @pytest.fixture(autouse=True)
     def shell(self, container):
         return Shell(container.pexpect())
 
     def test_true(self, shell):
-        """Simple command that has no effect just to test full process match.
-        """
+        """Simple command that has no effect just to test full process match."""
         shell.run("true")
 
     def test_false(self, shell):
-        """Simple command that has no effect but fails with known exit code.
-        """
+        """Simple command that has no effect but fails with known exit code."""
         assert shell.run("false", None) == 1
 
+    @pytest.fixture
+    def test_file(self, shell):
+        """This provides test file path that is removed after test finishes.
+        """
+        path = "/tmp/test-file"
+        yield path
+        shell.run(f"rm -f '{path}'")
+
+    def test_txt(self, request, shell, test_file):
+        """Check for simple use of txt_write and txt_read."""
+        txt = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
+        shell.txt_write(test_file, txt)
+        assert txt == shell.txt_read(test_file)
+
+    def test_txt_multiline(self, request, shell, test_file):
+        """Check for multiline use of txt_write and txt_read."""
+        txt = """Lorem ipsum dolor sit amet,
+        consectetur adipiscing elit,
+        sed do eiusmod tempor incididunt ut labore et
+        dolore magna aliqua. Ut enim ad minim veniam,
+        quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
+        commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident,
+        sunt in culpa qui officia deserunt mollit anim id est laborum."""
+        shell.txt_write(test_file, txt)
+        assert txt == shell.txt_read(test_file)
+
+    def test_txt_append(self, request, shell, test_file):
+        """Check if txt_read append option works as expected."""
+        txt1 = "First line"
+        txt2 = "Second line"
+        shell.txt_write(test_file, txt1)
+        shell.txt_write(test_file, txt2, append=True)
+        assert f"{txt1}\n{txt2}" == shell.txt_read(test_file)
+
+    def test_bin(self, request, shell, test_file):
+        """Check if txt_read append option works as expected."""
+        data = b'\x01\x02\x03\x04\x05'
+        shell.bin_write(test_file, data)
+        assert data == shell.bin_read(test_file)
+
 
 class TestOpenWrt(Common):
-    """These are tests in OpenWrt (ash) shell.
-    """
+    """These are tests in OpenWrt (ash) shell."""
 
     @pytest.fixture(scope="class", autouse=True)
     def container(self, lxd_connection):
@@ -36,8 +71,7 @@ class TestOpenWrt(Common):
 
 
 class TestAlpine(Common):
-    """These are tests in Alpine Linux (ash) shell.
-    """
+    """These are tests in Alpine Linux (ash) shell."""
 
     @pytest.fixture(scope="class", autouse=True)
     def container(self, lxd_connection):
-- 
GitLab