From 04971f2f29de59cf7f723bc1579426d9d30b1668 Mon Sep 17 00:00:00 2001 From: Edwin Eefting Date: Tue, 12 May 2020 01:25:25 +0200 Subject: [PATCH] working on unit tests, found and fixed escaping issue --- bin/test_executenode.py | 67 +++++++++++++++++++++++++++++++++++++++++ bin/zfs-autobackup | 19 ++++++++++-- 2 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 bin/test_executenode.py diff --git a/bin/test_executenode.py b/bin/test_executenode.py new file mode 100644 index 0000000..e260182 --- /dev/null +++ b/bin/test_executenode.py @@ -0,0 +1,67 @@ + +#default test stuff +import unittest +from zfs_autobackup import * + +import subprocess + +print("THIS TEST REQUIRES SSH TO LOCALHOST") + +class TestExecuteNode(unittest.TestCase): + + def setUp(self): + + return super().setUp() + + def basics(self, node ): + + #single line with spaces + self.assertEqual(node.run(["echo","test test"]), ["test test"]) + + #error exit code + with self.assertRaises(subprocess.CalledProcessError): + node.run(["false"]) + + #multiline without tabsplit + self.assertEqual(node.run(["echo","l1c1\tl1c2\nl2c1\tl2c2"], tab_split=False), ["l1c1\tl1c2", "l2c1\tl2c2"]) + # self.assertEqual(node.run(["echo","l1\nl2"]), ["l1", "l2"]) + + #multiline tabsplit + self.assertEqual(node.run(["echo","l1c1\tl1c2\nl2c1\tl2c2"], tab_split=True), [['l1c1', 'l1c2'], ['l2c1', 'l2c2']]) + + + #escaping (shouldnt be a problem locally, single quotes can be a problem remote via ssh) + s="><`'\"@&$()$bla\\//.*!#test" + self.assertEqual(node.run(["echo",s]), [s]) + + #return std err + # self.assertEqual(node.run(["echo","test test"], return_stderr=True), ["test test"]) + + + def test_basicslocal(self): + node=ExecuteNode(debug_output=True) + self.basics(node) + + def test_basicsremote(self): + node=ExecuteNode(ssh_to="localhost", debug_output=True) + self.basics(node) + + + # def test_remoteecho(self): + # node=ExecuteNode(ssh_to="localhost", debug_output=True) + # self.assertEqual(node.run(["echo","test"]), ["test"]) + + # def test_exitlocal(self): + # node=ExecuteNode(debug_output=True) + + # def test_exitremote(self): + # node=ExecuteNode(ssh_to="localhost", debug_output=True) + # with self.assertRaises(subprocess.CalledProcessError): + # self.remote1.run(["false"]) + + + + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/bin/zfs-autobackup b/bin/zfs-autobackup index 9fb0b79..5a47a0e 100755 --- a/bin/zfs-autobackup +++ b/bin/zfs-autobackup @@ -26,7 +26,7 @@ if sys.stdout.isatty(): except ImportError: pass -VERSION="3.0-rc10" +VERSION="3.0-rc11" HEADER="zfs-autobackup v{} - Copyright 2020 E.H.Eefting (edwin@datux.nl)\n".format(VERSION) class Log: @@ -299,6 +299,15 @@ class ExecuteNode: else: self.error("STDERR|> "+line.rstrip()) + #simple logging stubs + def debug(self, txt): + print("DEBUG : "+txt) + + def verbose(self, txt): + print("VERBOSE: "+txt) + + def error(self, txt): + print("ERROR : "+txt) def run(self, cmd, input=None, tab_split=False, valid_exitcodes=[ 0 ], readonly=False, hide_errors=False, pipe=False, return_stderr=False): """run a command on the node @@ -324,7 +333,9 @@ class ExecuteNode: #(this is necessary if LC_ALL=en_US.utf8 is not set in the environment) for arg in cmd: #add single quotes for remote commands to support spaces and other weird stuff (remote commands are executed in a shell) - encoded_cmd.append( ("'"+arg+"'").encode('utf-8')) + #and escape existing single quotes (bash needs ' to end the quoted string, then a \' for the actual quote and then another ' to start a new quoted string) + #(and then python needs the double \ to get a single \) + encoded_cmd.append( ("'" + arg.replace("'","'\\''") + "'").encode('utf-8')) else: for arg in cmd: @@ -369,6 +380,7 @@ class ExecuteNode: if isinstance(input,str) or type(input)=='unicode': p.stdin.write(input) + #return pipe if pipe: return(p) @@ -427,10 +439,11 @@ class ExecuteNode: if valid_exitcodes and input.returncode not in valid_exitcodes: raise(subprocess.CalledProcessError(input.returncode, "(pipe)")) - if valid_exitcodes and p.returncode not in valid_exitcodes: raise(subprocess.CalledProcessError(p.returncode, encoded_cmd)) + + if return_stderr: return ( output_lines, error_lines ) else: