Repository: rdiff-backup/rdiff-backup (language: python)

Issue:
[BUG] Duplicate ActionRemoveTest classes
## Bug summary

I just noticed that both `testing/action_remove_test.py` and `testing/action_verify_test.py` contain a class named `ActionRemoveTest`.  I'm guessing this is a copy/paste error and that the class in  `testing/action_verify_test.py` should be named `ActionVerifyTest`.

My apologies if I'm wrong and confused.


Discussion / hints:

Fix patch:
diff --git a/docs/Windows-DEVELOP.adoc b/docs/Windows-DEVELOP.adoc
index 9fee74ec6..f7f1a6b8a 100644
--- a/docs/Windows-DEVELOP.adoc
+++ b/docs/Windows-DEVELOP.adoc
@@ -158,6 +158,8 @@ Get a list of processes: `tasklist`
 
 Kill a process: `taskkill /IM firefox.exe /F`
 
+To get the return code of the last command (the equivalent of `$?` under Linux), call `echo %ERRORLEVEL%`.
+
 === Files handling
 
 To get a list of opened files, and the process accessing them:
@@ -171,6 +173,8 @@ openfiles /query /fo csv | find /I "<path>"
 
 With `icacls` you can list the (Windows) ACLs of a file.
 
+With `attrib` you can set the read-only attribute of a file.
+
 === Shells
 
 Different shells might be useful:
diff --git a/docs/rdiff-backup.1.adoc b/docs/rdiff-backup.1.adoc
index fd731db63..9c3208981 100644
--- a/docs/rdiff-backup.1.adoc
+++ b/docs/rdiff-backup.1.adoc
@@ -228,7 +228,7 @@ Start the server in debug mode so that it stops on an early breakpoint and can b
 See the https://github.com/rdiff-backup/rdiff-backup/blob/master/docs/DEVELOP.adoc#debug-client-server-mode[developer's documentation] for details.
 
 test _remote_location_1_ [_remote_location_2_ ...]::
-Test for the presence of a compatible rdiff-backup server as specified in the following remote location argument(s) (of which the filename section will be ignored).
+Test for the presence of a compatible rdiff-backup server as specified in the remote location argument(s) (of which the filename section will be checked for existence).
 See the <<_remote_operation,REMOTE OPERATION>> section for details.
 
 verify [*--at* _time_] _location_::
diff --git a/src/rdiff_backup/FilenameMapping.py b/src/rdiff_backup/FilenameMapping.py
index ee08b322b..f5ea54a8f 100644
--- a/src/rdiff_backup/FilenameMapping.py
+++ b/src/rdiff_backup/FilenameMapping.py
@@ -31,7 +31,8 @@
 
 import os
 import re
-from . import Globals, log, rpath
+from rdiff_backup import Globals, log, rpath
+from rdiffbackup.utils import safestr
 
 # If true, enable character quoting, and set characters making
 # regex-style range.
@@ -132,7 +133,7 @@ def set_init_quote_vals_local():
     if len(Globals.quoting_char) != 1:
         log.Log.FatalError("Expected single character for quoting char, "
                            "got '{qc}' instead.".format(
-                               qc=_safe_str(Globals.quoting_char)))
+                               qc=safestr.to_str(Globals.quoting_char)))
     quoting_char = Globals.quoting_char
     _init_quoting_regexps()
 
@@ -191,7 +192,7 @@ def _init_quoting_regexps():
     global chars_to_quote_regexp, unquoting_regexp
     assert chars_to_quote and isinstance(chars_to_quote, bytes), (
         "Chars to quote must be non-empty bytes: '{ctq}'.".format(
-            ctq=_safe_str(chars_to_quote)))
+            ctq=safestr.to_str(chars_to_quote)))
     try:
         chars_to_quote_regexp = re.compile(b"[%b]|%b" %
                                            (chars_to_quote, quoting_char), re.S)
@@ -210,16 +211,10 @@ def _quote_single(match):
 def _unquote_single(match):
     """Unquote a single quoted character"""
     if not len(match.group()) == 4:
-        raise QuotingException("Quoted group wrong size: '%s'." % _safe_str(match.group()))
+        raise QuotingException("Quoted group wrong size: '{qg}'".format(
+            qg=safestr.to_str(match.group())))
     try:
         return os.fsencode(chr(int(match.group()[1:])))
     except ValueError:
-        raise QuotingException("Quoted out of range: '%s'." % _safe_str(match.group()))
-
-
-def _safe_str(cmd):
-    """Transform bytes into string without risk of conversion error"""
-    if isinstance(cmd, str):
-        return cmd
-    else:
-        return str(cmd, errors='replace')
+        raise QuotingException("Quoted out of range: '{qg}'".format(
+            qg=safestr.to_str(match.group())))
diff --git a/src/rdiff_backup/Globals.py b/src/rdiff_backup/Globals.py
index 213181989..f9500bed6 100644
--- a/src/rdiff_backup/Globals.py
+++ b/src/rdiff_backup/Globals.py
@@ -321,7 +321,7 @@ def set_all(setting_name, value):
     Where set relies on each connection to grab the value at a later stage,
     set_all forces the value on all connections at once.
     """
-    for conn in connections:
+    for conn in connection_dict.values():
         conn.Globals.set_local(setting_name, value)
 
 
diff --git a/src/rdiff_backup/SetConnections.py b/src/rdiff_backup/SetConnections.py
index bfeefa6ea..b2143a432 100644
--- a/src/rdiff_backup/SetConnections.py
+++ b/src/rdiff_backup/SetConnections.py
@@ -29,6 +29,7 @@
 import sys
 import subprocess
 from rdiff_backup import connection, Globals, log, rpath
+from rdiffbackup.utils import safestr
 
 # This is a list of remote commands used to start the connections.
 # The first is None because it is the local connection.
@@ -294,7 +295,7 @@ def _fill_schema(host_info, cmd_schema):
             return cmd_schema % host_info
     except (TypeError, KeyError):
         log.Log.FatalError("Invalid remote schema: {rs}".format(
-            rs=_safe_str(cmd_schema)))
+            rs=safestr.to_str(cmd_schema)))
 
 
 def _init_connection(remote_cmd):
@@ -309,8 +310,8 @@ def _init_connection(remote_cmd):
     if not remote_cmd:
         return Globals.local_connection
 
-    log.Log("Executing remote command {rc}".format(rc=_safe_str(remote_cmd)),
-            log.INFO)
+    log.Log("Executing remote command {rc}".format(
+        rc=safestr.to_str(remote_cmd)), log.INFO)
     try:
         # we need buffered read on SSH communications, hence using
         # default value for bufsize parameter
@@ -362,7 +363,7 @@ def _validate_connection_version(conn, remote_cmd):
 installed in the PATH on the remote system.  See the man page for more
 information on this.  This message may also be displayed if the remote
 version of rdiff-backup is quite different from the local version ({lv})
-""".format(ex=exception, rc=_safe_str(remote_cmd), lv=Globals.version),
+""".format(ex=exception, rc=safestr.to_str(remote_cmd), lv=Globals.version),
             log.ERROR)
         return False
     except OverflowError:
@@ -374,8 +375,9 @@ def _validate_connection_version(conn, remote_cmd):
 command executes. Try running this command: {co}
 
 which should only print out the text: rdiff-backup <version>""".format(
-                rc=_safe_str(remote_cmd),
-                co=_safe_str(remote_cmd.replace(b"--server", b"--version"))),
+                rc=safestr.to_str(remote_cmd),
+                co=safestr.to_str(
+                    remote_cmd.replace(b"--server", b"--version"))),
             log.ERROR)
         return False
 
@@ -490,21 +492,10 @@ def _test_connection(conn_number, rp):
     # on Windows but not on Linux? What are we exactly testing here?
     try:
         assert conn.Globals.get('current_time') is None
-        try:
-            assert type(conn.os.getuid()) is int
-        except AttributeError:  # Windows doesn't support os.getuid()
-            assert type(conn.os.listdir(rp.path)) is list
+        assert type(conn.os.listdir(rp.path)) is list
     except BaseException as exc:
         sys.stderr.write("- Server tests failed due to {exc}\n".format(exc=exc))
         return False
     else:
         print("- Server OK")
         return True
-
-
-def _safe_str(cmd):
-    """Transform bytes into string without risk of conversion error"""
-    if isinstance(cmd, str):
-        return cmd
-    else:
-        return str(cmd, errors='replace')
diff --git a/src/rdiff_backup/longname.py b/src/rdiff_backup/longname.py
index dabea88b5..c9f0da9ef 100644
--- a/src/rdiff_backup/longname.py
+++ b/src/rdiff_backup/longname.py
@@ -40,6 +40,7 @@
 
 import errno
 from rdiff_backup import Globals, log
+from rdiffbackup.utils import safestr
 
 _long_name_dir = b"long_filename_data"
 _long_name_rootrp = None
@@ -165,17 +166,10 @@ def update_rf(rf, rorp, mirror_root, rf_class):
     rf_class is the object type to return, RestoreFile or RegressFile
     """
 
-    def _safe_str(cmd):
-        """Transform bytes into string without risk of conversion error"""
-        if isinstance(cmd, str):
-            return cmd
-        else:
-            return str(cmd, errors='replace')
-
     def update_incs(rf, inc_base):
         """Swap inclist in rf with those with base inc_base and return"""
         log.Log("Restoring with increment base {ib} for file {rp}".format(
-            ib=_safe_str(inc_base), rp=rf), log.DEBUG)
+            ib=safestr.to_str(inc_base), rp=rf), log.DEBUG)
         rf.inc_rp = _get_long_rp(inc_base)
         rf.inc_list = _get_inclist(inc_base, rf_class)
         rf.set_relevant_incs()
diff --git a/src/rdiff_backup/rpath.py b/src/rdiff_backup/rpath.py
index b17decd92..d5d19b48b 100644
--- a/src/rdiff_backup/rpath.py
+++ b/src/rdiff_backup/rpath.py
@@ -634,7 +634,6 @@ class RPath(RORPath):
     identification (i.e. if two files have the the same (==) data
     dictionary, they are the same file).
     """
-    _regex_chars_to_quote = re.compile(b"[\\\\\\\"\\$`]")
     # class index to make temporary files unique, see get_temp_rpath
     _temp_file_index = 0
 
@@ -930,11 +929,6 @@ def contains_files(self):
                     return True
         return False
 
-    def quote(self):
-        """Return quoted self.path for use with os.system()"""
-        return b'"%s"' % self._regex_chars_to_quote.sub(
-            lambda m: b"\\" + m.group(0), self.path)
-
     def normalize(self):
         """Return RPath canonical version of self.path
 
diff --git a/src/rdiff_backup/run_stats.py b/src/rdiff_backup/run_stats.py
index e56a42037..b121d7758 100755
--- a/src/rdiff_backup/run_stats.py
+++ b/src/rdiff_backup/run_stats.py
@@ -32,6 +32,7 @@
     rpath,
     Time,
 )
+from rdiffbackup.utils import safestr
 
 begin_time = None  # Parse statistics at or after this time...
 end_time = None  # ... and at or before this time (epoch seconds)
@@ -101,7 +102,7 @@ def version(rc):
 def os_system(cmd):
     sys.stdout.flush()
     if subprocess.call(cmd):
-        sys.exit("Error running command '%s'\n" % _safe_str(cmd))
+        sys.exit("Error running command '{rc}'".format(rc=safestr.to_str(cmd)))
 
 
 class StatisticsRPaths:
@@ -312,7 +313,8 @@ def _yield_fs_objs(filestatsobj):
             continue
         match = r.match(line)
         if not match:
-            sys.stderr.write("Error parsing line: %s\n" % _safe_str(line))
+            sys.stderr.write("Error parsing line: '{li}'\n".format(
+                li=safestr.to_str(line)))
             continue
 
         filename = match.group(1)
@@ -547,14 +549,6 @@ def set_chars_to_quote():
         Globals.rbdir = FilenameMapping.get_quotedrpath(Globals.rbdir)
 
 
-def _safe_str(cmd):
-    """Transform bytes into string without risk of conversion error"""
-    if isinstance(cmd, str):
-        return cmd
-    else:
-        return str(cmd, errors='replace')
-
-
 def main_run():
     Time.set_current_time()
     parse_args()
diff --git a/src/rdiff_backup/selection.py b/src/rdiff_backup/selection.py
index 394e14304..1c6e7d3b3 100644
--- a/src/rdiff_backup/selection.py
+++ b/src/rdiff_backup/selection.py
@@ -26,7 +26,8 @@
 import os
 import re
 import sys
-from . import robust, rpath, Globals, log, rorpiter
+from rdiff_backup import Globals, log, robust, rorpiter, rpath
+from rdiffbackup.utils import safestr
 
 
 class SelectError(Exception):
@@ -693,18 +694,12 @@ def exclude_sel_func(rp):
     def _glob_get_prefix_res(self, glob_str):
         """Return list of regexps equivalent to prefixes of glob_str"""
 
-        def _safe_str(cmd):
-            """Transform bytes into string without risk of conversion error"""
-            if isinstance(cmd, str):
-                return cmd
-            else:
-                return str(cmd, errors='replace')
-
         glob_parts = glob_str.split(b"/")
         if b"" in glob_parts[1:
                              -1]:  # "" OK if comes first or last, as in /foo/
             raise GlobbingError(
-                "Consecutive '/'s found in globbing string %s" % _safe_str(glob_str))
+                "Consecutive '/'s found in globbing string {gs}".format(
+                    gs=safestr.to_str(glob_str)))
 
         prefixes = [
             b"/".join(glob_parts[:i + 1]) for i in range(len(glob_parts))
diff --git a/src/rdiffbackup/actions/complete.py b/src/rdiffbackup/actions/complete.py
index a14c96153..a0e604cd8 100644
--- a/src/rdiffbackup/actions/complete.py
+++ b/src/rdiffbackup/actions/complete.py
@@ -203,6 +203,7 @@ def _get_matching_options(self, options, args, words, cword, unique=True):
                     return prev_option.choices
                 elif (isinstance(prev_option.type, argparse.FileType)
                         or (prev_option.type == str
+                            and isinstance(prev_option.metavar, str)
                             and prev_option.metavar.endswith('_FILE'))):
                     return ["::file::"]
                 else:  # we can't make any statement about the parameter
diff --git a/src/rdiffbackup/locations/map/filenames.py b/src/rdiffbackup/locations/map/filenames.py
index d5f41bb6b..8057f6a33 100644
--- a/src/rdiffbackup/locations/map/filenames.py
+++ b/src/rdiffbackup/locations/map/filenames.py
@@ -31,6 +31,7 @@
 import os
 import re
 from rdiff_backup import Globals, log, rpath
+from rdiffbackup.utils import safestr
 
 
 class QuotingException(Exception):
@@ -197,18 +198,10 @@ def _unquote_single(match):
     Unquote a single quoted character
     """
     if not len(match.group()) == 4:
-        raise QuotingException("Quoted group wrong size: '%s'." % _safe_str(match.group()))
+        raise QuotingException("Quoted group wrong size: '{qg}'".format(
+            qg=safestr.to_str(match.group())))
     try:
         return os.fsencode(chr(int(match.group()[1:])))
     except ValueError:
-        raise QuotingException("Quoted out of range: '%s'." % _safe_str(match.group()))
-
-
-def _safe_str(cmd):
-    """
-    Transform bytes into string without risk of conversion error
-    """
-    if isinstance(cmd, str):
-        return cmd
-    else:
-        return str(cmd, errors='replace')
+        raise QuotingException("Quoted out of range: '{qg}'".format(
+            qg=safestr.to_str(match.group())))
diff --git a/src/rdiffbackup/locations/map/longnames.py b/src/rdiffbackup/locations/map/longnames.py
index c7e3d6e16..ce1adafcc 100644
--- a/src/rdiffbackup/locations/map/longnames.py
+++ b/src/rdiffbackup/locations/map/longnames.py
@@ -39,6 +39,7 @@
 
 import errno
 from rdiff_backup import Globals, log
+from rdiffbackup.utils import safestr
 
 _long_name_dir = b"long_filename_data"
 _long_name_rootrp = None
@@ -172,21 +173,12 @@ def update_rf(rf, rorp, mirror_root, rf_class):
     rf_class is the object type to return, RestoreFile or RegressFile
     """
 
-    def _safe_str(cmd):
-        """
-        Transform bytes into string without risk of conversion error
-        """
-        if isinstance(cmd, str):
-            return cmd
-        else:
-            return str(cmd, errors='replace')
-
     def update_incs(rf, inc_base):
         """
         Swap inclist in rf with those with base inc_base and return
         """
         log.Log("Restoring with increment base {ib} for file {rp}".format(
-            ib=_safe_str(inc_base), rp=rf), log.DEBUG)
+            ib=safestr.to_str(inc_base), rp=rf), log.DEBUG)
         rf.inc_rp = _get_long_rp(inc_base)
         rf.inc_list = _get_inclist(inc_base, rf_class)
         rf.set_relevant_incs()
diff --git a/src/rdiffbackup/meta/acl_win.py b/src/rdiffbackup/meta/acl_win.py
index e5374e175..32d61f41a 100644
--- a/src/rdiffbackup/meta/acl_win.py
+++ b/src/rdiffbackup/meta/acl_win.py
@@ -21,6 +21,7 @@
 import os
 from rdiff_backup import C, Globals, log, rorpiter
 from rdiffbackup import meta
+from rdiffbackup.utils import safestr
 
 try:
     from win32security import (
@@ -183,18 +184,11 @@ def write_to_rp(self, rp):
                     "due to exception '{ex}'".format(pa=rp, ex=exc), log.INFO)
 
     def from_string(self, acl_str):
-
-        def _safe_str(cmd):
-            """Transform bytes into string without risk of conversion error"""
-            if isinstance(cmd, str):
-                return cmd
-            else:
-                return str(cmd, errors='replace')
-
         lines = acl_str.splitlines()
         if len(lines) != 2 or not lines[0][:8] == b"# file: ":
             raise meta.ParsingError(
-                "Bad record beginning: %s" % _safe_str(lines[0][:8]))
+                "Bad record beginning: '{br}'".format(
+                    br=safestr.to_str(lines[0][:8])))
         filename = lines[0][8:]
         if filename == b'.':
             self.index = ()
diff --git a/src/rdiffbackup/meta/ea.py b/src/rdiffbackup/meta/ea.py
index c484534d2..25863b85b 100644
--- a/src/rdiffbackup/meta/ea.py
+++ b/src/rdiffbackup/meta/ea.py
@@ -39,6 +39,7 @@
 
 from rdiff_backup import C, Globals, log, rorpiter
 from rdiffbackup import meta
+from rdiffbackup.utils import safestr
 
 
 class ExtendedAttributes:
@@ -240,17 +241,9 @@ def _object_to_record(ea):
     @staticmethod
     def join_iter(rorp_iter, ea_iter):
         """Update a rorp iter by adding the information from ea_iter"""
-
-        def _safe_str(cmd):
-            """Transform bytes into string without risk of conversion error"""
-            if isinstance(cmd, str):
-                return cmd
-            else:
-                return str(cmd, errors='replace')
-
         for rorp, ea in rorpiter.CollateIterators(rorp_iter, ea_iter):
             assert rorp, ("Missing rorp for EA index '{eaidx}'.".format(
-                eaidx=map(_safe_str, ea.index)))
+                eaidx=map(safestr.to_str, ea.index)))
             if not ea:
                 ea = get_meta_object(rorp.index)
             rorp.set_ea(ea)
diff --git a/src/rdiffbackup/utils/safestr.py b/src/rdiffbackup/utils/safestr.py
new file mode 100644
index 000000000..66d477669
--- /dev/null
+++ b/src/rdiffbackup/utils/safestr.py
@@ -0,0 +1,29 @@
+# Copyright 2002 Ben Escoto
+#
+# This file is part of rdiff-backup.
+#
+# rdiff-backup is free software; you can redistribute it and/or modify
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# rdiff-backup is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with rdiff-backup; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA
+"""
+Simple function to safely transform bytes and other objects to string
+"""
+
+
+def to_str(something):
+    """Transform bytes into string without risk of conversion error"""
+    if isinstance(something, bytes):
+        return str(something, errors='replace')
+    else:
+        return str(something)
diff --git a/tox_win.ini b/tox_win.ini
index ea7dc7e1a..2369f4781 100644
--- a/tox_win.ini
+++ b/tox_win.ini
@@ -25,14 +25,14 @@ commands =
 # The commented tests do not run on Windows yet and will be fixed & uncommented one by one
     python testing/action_backuprestore_test.py --verbose --buffer
     python testing/action_calculate_test.py --verbose --buffer
-#    python testing/action_compare_test.py --verbose --buffer
+    python testing/action_compare_test.py --verbose --buffer
     python testing/action_list_test.py --verbose --buffer
     python testing/action_regress_test.py --verbose --buffer
     python testing/action_remove_test.py --verbose --buffer
-#    python testing/action_test_test.py --verbose --buffer
+    python testing/action_test_test.py --verbose --buffer
     python testing/action_verify_test.py --verbose --buffer
     python testing/api_test.py --verbose --buffer
-#    python testing/location_map_filenames_test.py --verbose --buffer
+    python testing/location_map_filenames_test.py --verbose --buffer
 #    python testing/location_map_hardlinks_test.py --verbose --buffer
     python testing/location_lock_test.py --verbose --buffer
     python testing/utils_simpleps_test.py --verbose --buffer
@@ -46,7 +46,7 @@ commands =
 #    python testing/longnametest.py  # handling of long filenames too different
     python testing/robusttest.py
     python testing/connectiontest.py
-#    python testing/incrementtest.py  # issue with symlinks
+    python testing/incrementtest.py  # skipping symlinks
 #    python testing/hardlinktest.py  # too many path errors
 #    python testing/eas_aclstest.py  # no module named pwd under Windows
 #    python testing/FilenameMappingtest.py  # issues with : in date/time-string
@@ -54,13 +54,13 @@ commands =
     python testing/hashtest.py
     python testing/selectiontest.py
 #    python testing/metadatatest.py  # issues with : in date/time/string
-#    python testing/rpathtest.py  # many small issues
+    python testing/rpathtest.py
     python testing/rorpitertest.py
     python testing/rdifftest.py
 #    python testing/securitytest.py  # no os.getuid + backslash in path handlingon --server
 #    python testing/killtest.py  # cannot create symbolic link
 #    python testing/backuptest.py  # No module named rdiff_backup in server.py
-#    python testing/comparetest.py  # No module named rdiff_backup in server.py
+    python testing/comparetest.py
 #    python testing/regresstest.py  # issue with : in date/time/string
 #    python testing/restoretest.py  # too many issues to count
 #    python testing/cmdlinetest.py  # too many issues to count


Test patch:
diff --git a/testing/action_compare_test.py b/testing/action_compare_test.py
index 2f08fa044..cb4813409 100644
--- a/testing/action_compare_test.py
+++ b/testing/action_compare_test.py
@@ -90,9 +90,10 @@ def test_action_compare(self):
             True, False, self.from2_path, self.bak_path,
             ("--api-version", "201"),
             b"compare", ("--at", "now", "--method", "hash")), 0)
+        # reduce verbosity to avoid file system quoting notes under Windows
         self.assertEqual(comtst.rdiff_backup_action(
             False, False, self.from3_path, self.bak_path,
-            ("--api-version", "201", "--parsable"),
+            ("--api-version", "201", "--parsable", "-v2"),
             b"compare", ("--method", "hash"), return_stdout=True),
             b"""---
 - path: fileChanged
@@ -110,9 +111,10 @@ def test_action_compare(self):
             True, False, self.from1_path, self.bak_path,
             ("--api-version", "201"),
             b"compare", ("--at", "1B", "--method", "full")), 0)
+        # reduce verbosity to avoid file system quoting notes under Windows
         self.assertEqual(comtst.rdiff_backup_action(
             True, True, self.from3_path, self.bak_path,
-            ("--api-version", "201", "--parsable"),
+            ("--api-version", "201", "--parsable", "-v2"),
             b"compare", ("--method", "full"), return_stdout=True),
             b"""---
 - path: fileChanged
diff --git a/testing/action_list_test.py b/testing/action_list_test.py
index c010741e9..930c541c9 100644
--- a/testing/action_list_test.py
+++ b/testing/action_list_test.py
@@ -10,7 +10,7 @@
 
 class ActionListTest(unittest.TestCase):
     """
-    Test that rdiff-backup really restores what has been backed-up
+    Test that rdiff-backup properly lists files and increments
     """
 
     def setUp(self):
diff --git a/testing/action_remove_test.py b/testing/action_remove_test.py
index 8f8f462c4..644454996 100644
--- a/testing/action_remove_test.py
+++ b/testing/action_remove_test.py
@@ -11,7 +11,7 @@
 
 class ActionRemoveTest(unittest.TestCase):
     """
-    Test that rdiff-backup really restores what has been backed-up
+    Test that rdiff-backup properly removes increments
     """
 
     def setUp(self):
diff --git a/testing/action_test_test.py b/testing/action_test_test.py
index ff2916824..bae6ed644 100644
--- a/testing/action_test_test.py
+++ b/testing/action_test_test.py
@@ -1,24 +1,32 @@
+import os
 import unittest
 import commontest as comtst
 
 
 class ActionTestTest(unittest.TestCase):
     """Test api versioning functionality"""
+    def setUp(self):
+        if os.name == "nt":
+            self.dir1 = b"C:\\"
+            self.dir2 = b"C:\\"
+        else:
+            self.dir1 = b"/tmp"
+            self.dir2 = b"/var/tmp"
 
     def test_action_test(self):
         """test the "test" action"""
         # the test action works with one or two locations (or more)
         self.assertEqual(
-            comtst.rdiff_backup_action(False, False, b"/dummy1", b"/dummy2",
+            comtst.rdiff_backup_action(False, False, self.dir1, self.dir2,
                                        (), b"test", ()),
             0)
         self.assertEqual(
-            comtst.rdiff_backup_action(False, True, b"/dummy1", None,
+            comtst.rdiff_backup_action(False, True, self.dir1, None,
                                        (), b"test", ()),
             0)
         # but it doesn't work with a local one
         self.assertNotEqual(
-            comtst.rdiff_backup_action(False, True, b"/dummy1", b"/dummy2",
+            comtst.rdiff_backup_action(False, True, self.dir1, self.dir2,
                                        (), b"test", ()),
             0)
         # and it doesn't work without any location
diff --git a/testing/action_verify_test.py b/testing/action_verify_test.py
index 14946c343..d1a576b57 100644
--- a/testing/action_verify_test.py
+++ b/testing/action_verify_test.py
@@ -8,9 +8,9 @@
 import fileset
 
 
-class ActionRemoveTest(unittest.TestCase):
+class ActionVerifyTest(unittest.TestCase):
     """
-    Test that rdiff-backup really restores what has been backed-up
+    Test that rdiff-backup properly verifies repositories
     """
 
     def setUp(self):
diff --git a/testing/cmdlinetest.py b/testing/cmdlinetest.py
index 8a7109e51..8a8e9bc9d 100644
--- a/testing/cmdlinetest.py
+++ b/testing/cmdlinetest.py
@@ -325,6 +325,8 @@ def delete_long(base_rp, length=100):
             compare_recursive(Local.wininc3, Local.rpout3, compare_hardlinks=0))
         self.rb_schema = old_schema
 
+    @unittest.skipIf(os.name == "nt",
+                     "Windows doesn't recognize case insensitivity")
     def testLegacy(self):
         """Test restoring directory with no mirror_metadata file"""
         self.delete_tmpdirs()
diff --git a/testing/commontest.py b/testing/commontest.py
index 313394896..64ba2daf8 100644
--- a/testing/commontest.py
+++ b/testing/commontest.py
@@ -38,6 +38,9 @@
 
 if os.name == "nt":
     Globals.use_compatible_timestamps = 1
+    CMD_SEP = b" & "
+else:
+    CMD_SEP = b" ; "
 
 
 def Myrm(dirstring):
@@ -46,6 +49,8 @@ def Myrm(dirstring):
     for rp in selection.Select(root_rp).get_select_iter():
         if rp.isdir():
             rp.chmod(0o700)  # otherwise may not be able to remove
+        elif rp.isreg():
+            rp.chmod(0o600)  # Windows can't remove read-only files
     path = root_rp.path
     if os.path.isdir(path):
         shutil.rmtree(path)
@@ -110,12 +115,12 @@ def rdiff_backup(source_local,
 
     If expected_ret_code is set to None, no return value comparaison is done.
     """
+    remote_exec = CMD_SEP.join([b'"cd %s', b'%s server::%s"'])
+
     if not source_local:
-        src_dir = (b'"cd %s; %s server::%s"' %
-                   (abs_remote1_dir, RBBin, src_dir))
+        src_dir = (remote_exec % (abs_remote1_dir, RBBin, src_dir))
     if dest_dir and not dest_local:
-        dest_dir = (b'"cd %s; %s server::%s"' %
-                    (abs_remote2_dir, RBBin, dest_dir))
+        dest_dir = (remote_exec % (abs_remote2_dir, RBBin, dest_dir))
 
     cmdargs = [RBBin, extra_options]
     if not (source_local and dest_local):
@@ -156,10 +161,7 @@ def rdiff_backup_action(source_local, dest_local,
     The std_input parameter is optional and used to provide the call to
     rdiff-backup with pre-defined input.
     """
-    if os.name == "nt":
-        remote_exec = b"cd %s & %s server::%s"
-    else:
-        remote_exec = b"cd %s ; %s server::%s"
+    remote_exec = CMD_SEP.join([b"cd %s", b"%s server::%s"])
 
     if src_dir and not source_local:
         src_dir = (remote_exec % (abs_remote1_dir, RBBin, src_dir))
@@ -421,10 +423,18 @@ def _acl_compare_rps(rp1, rp2):
 
 def _files_rorp_eq(src_rorp, dest_rorp,
                    compare_hardlinks=True,
+                   compare_symlinks=None,
                    compare_ownership=False,
                    compare_eas=False,
                    compare_acls=False):
     """Combined eq func returns true if two files compare same"""
+    # default value depends on OS, symlinks aren't supported under Windows
+    if compare_symlinks is None:
+        compare_symlinks = (os.name != "nt")
+    if not compare_symlinks:
+        if (src_rorp and src_rorp.issym()
+                or dest_rorp and dest_rorp.issym()):
+            return True
     if not src_rorp:
         log.Log("Source rorp missing: %s" % str(dest_rorp), 3)
         return False
diff --git a/testing/comparetest.py b/testing/comparetest.py
index a63240517..9ae3abdd1 100644
--- a/testing/comparetest.py
+++ b/testing/comparetest.py
@@ -5,6 +5,9 @@
 from rdiff_backup import Globals, rpath
 """Test the compare.py module and overall compare functionality"""
 
+# FIXME the remote comparison tests have to be skipped under Windows
+# see https://github.com/rdiff-backup/rdiff-backup/issues/788
+
 
 class CompareTest(unittest.TestCase):
     def setUp(self):
@@ -42,6 +45,7 @@ def testBasicLocal(self):
         """Test basic --compare and --compare-at-time modes"""
         self.generic_test(1, b"--compare")
 
+    @unittest.skipIf(os.name == "nt", "Symlinks not supported under Windows")
     def testBasicRemote(self):
         """Test basic --compare and --compare-at-time modes, both remote"""
         self.generic_test(0, b"--compare")
@@ -50,6 +54,7 @@ def testHashLocal(self):
         """Test --compare-hash and --compare-hash-at-time modes local"""
         self.generic_test(1, b"--compare-hash")
 
+    @unittest.skipIf(os.name == "nt", "Symlinks not supported under Windows")
     def testHashRemote(self):
         """Test --compare-hash and -at-time remotely"""
         self.generic_test(0, b"--compare-hash")
@@ -58,6 +63,7 @@ def testFullLocal(self):
         """Test --compare-full and --compare-full-at-time"""
         self.generic_test(1, b"--compare-full")
 
+    @unittest.skipIf(os.name == "nt", "Symlinks not supported under Windows")
     def testFullRemote(self):
         """Test full file compare remotely"""
         self.generic_test(0, b"--compare-full")
@@ -94,6 +100,7 @@ def testSelLocal(self):
         """Test basic local compare of single subdirectory"""
         self.generic_selective_test(1, b"--compare")
 
+    @unittest.skipIf(os.name == "nt", "Symlinks not supported under Windows")
     def testSelRemote(self):
         """Test --compare of single directory, remote"""
         self.generic_selective_test(0, b"--compare")
@@ -102,6 +109,7 @@ def testSelHashLocal(self):
         """Test --compare-hash of subdirectory, local"""
         self.generic_selective_test(1, b"--compare-hash")
 
+    @unittest.skipIf(os.name == "nt", "Symlinks not supported under Windows")
     def testSelHashRemote(self):
         """Test --compare-hash of subdirectory, remote"""
         self.generic_selective_test(0, b"--compare-hash")
@@ -110,6 +118,7 @@ def testSelFullLocal(self):
         """Test --compare-full of subdirectory, local"""
         self.generic_selective_test(1, b"--compare-full")
 
+    @unittest.skipIf(os.name == "nt", "Symlinks not supported under Windows")
     def testSelFullRemote(self):
         """Test --compare-full of subdirectory, remote"""
         self.generic_selective_test(0, b"--compare-full")
diff --git a/testing/incrementtest.py b/testing/incrementtest.py
index 9389fda03..cc9dcffb8 100644
--- a/testing/incrementtest.py
+++ b/testing/incrementtest.py
@@ -14,15 +14,10 @@ def getrp(ending):
         lc, os.path.join(old_test_dir, b"various_file_types", ending))
 
 
+base_dir = getrp(b".")
 rf = getrp(b"regular_file")
 rf2 = getrp(b"two_hardlinked_files1")
 exec1 = getrp(b"executable")
-exec2 = getrp(b"executable2")
-sig = getrp(b"regular_file.sig")
-hl1, hl2 = list(
-    map(getrp, [b"two_hardlinked_files1", b"two_hardlinked_files2"]))
-test = getrp(b"test")
-dir = getrp(b".")
 sym = getrp(b"symbolic_link")
 nothing = getrp(b"nothing")
 
@@ -36,7 +31,6 @@ def getrp(ending):
     prevtimestr = b"2001-09-02T02-48-33-07-00"
 else:
     prevtimestr = b"2001-09-02T02:48:33-07:00"
-t_pref = os.path.join(abs_output_dir, b"out.%s" % prevtimestr)
 t_diff = os.path.join(abs_output_dir, b"out.%s.diff" % prevtimestr)
 
 Globals.postset_regexp_local("no_compression_regexp",
@@ -81,6 +75,7 @@ def testmissing(self):
         self.assertEqual(missing_rp.getinctype(), b'missing')
         missing_rp.delete()
 
+    @unittest.skipIf(os.name == "nt", "Symlinks not supported under Windows")
     def testsnapshot(self):
         """Test making of a snapshot"""
         Globals.compression = None
@@ -96,6 +91,7 @@ def testsnapshot(self):
         self.assertTrue(rpath.cmp(snap_rp2, rf))
         snap_rp2.delete()
 
+    @unittest.skipIf(os.name == "nt", "Symlinks not supported under Windows")
     def testGzipsnapshot(self):
         """Test making a compressed snapshot"""
         Globals.compression = 1
@@ -113,16 +109,16 @@ def testGzipsnapshot(self):
         self.assertTrue(rp.isinccompressed())
         rp.delete()
 
+    @unittest.skipIf(os.name == "nt", "Symlinks not supported under Windows")
     def testdir(self):
-        """Test increment on dir"""
-        rp = increment.Increment(sym, dir, target)
+        """Test increment on base_dir"""
+        rp = increment.Increment(sym, base_dir, target)
         self.check_time(rp)
         self.assertTrue(rp.lstat())
         self.assertTrue(target.isdir())
-        self.assertTrue(dir._equal_verbose(rp,
-                                           check_index=0,
-                                           compare_size=0,
-                                           compare_type=0))
+        self.assertTrue(base_dir._equal_verbose(rp, check_index=0,
+                                                compare_size=0,
+                                                compare_type=0))
         self.assertTrue(rp.isreg())
         rp.delete()
         target.delete()
diff --git a/testing/location_map_filenames_test.py b/testing/location_map_filenames_test.py
index 6a7f16fc1..2674428dc 100644
--- a/testing/location_map_filenames_test.py
+++ b/testing/location_map_filenames_test.py
@@ -17,6 +17,8 @@ class LocationMapFilenamesTest(unittest.TestCase):
     def setUp(self):
         self.base_dir = os.path.join(comtst.abs_test_dir,
                                      b"location_map_filenames")
+        # Windows can't handle too long filenames
+        long_multi = 5 if os.name == "nt" else 25
         self.from1_struct = {
             "from1": {"subs": {
                 "fileABC": {"content": "initial"},
@@ -24,8 +26,10 @@ def setUp(self):
                 "dirAbCXyZ": {"type": "dir"},
                 "itemX": {"type": "dir"},
                 "itemY": {"type": "file"},
-                "longDiRnAm" * 25: {"type": "dir"},
-                "longFiLnAm" * 25: {"content": "not so long content"},
+                "longDiRnAm" * long_multi: {"type": "dir"},
+                "longFiLnAm" * long_multi: {"content": "not so long content"},
+                # it should be aux.123 but it can't be created under Windows
+                # and isn't special under Linux
                 "aux123": {"content": "looks like DOS file"},
                 "ends_in_blank ": {"content": "looks like DOS file"},
             }}
@@ -38,8 +42,10 @@ def setUp(self):
                 "diraBcXyZ": {"type": "dir"},
                 "itemX": {"type": "file"},
                 "itemY": {"type": "dir"},
-                "longDiRnAm" * 25: {"type": "dir"},
-                "longFiLnAm" * 25: {"content": "differently long"},
+                "longDiRnAm" * long_multi: {"type": "dir"},
+                "longFiLnAm" * long_multi: {"content": "differently long"},
+                # it should be aux.123 but it can't be created under Windows
+                # and isn't special under Linux
                 "aux123": {"content": "still looks like DOS file"},
                 "ends_in_blank ": {"content": "still looks like DOS file"},
             }}
diff --git a/testing/rpathtest.py b/testing/rpathtest.py
index fa50e9454..19d95e7d5 100644
--- a/testing/rpathtest.py
+++ b/testing/rpathtest.py
@@ -86,6 +86,7 @@ def testBlockDev(self):
             rpath.RPath(self.lc, self.prefix, ("regular_file", )).isblkdev())
 
 
+@unittest.skipIf(os.name == "nt", "Windows doesn't handle well rights")
 class CheckPerms(RPathTest):
     """Check to see if permissions are reported and set accurately"""
 
@@ -135,6 +136,7 @@ def testSet(self):
         self.assertEqual(rp.getmtime(), 20000)
         rp.delete()
 
+    @unittest.skipIf(os.name == "nt", "Windows doesn't handle ctime correctly")
     def testCtime(self):
         """Check to see if ctime read, compared"""
         rp = rpath.RPath(self.lc, self.prefix, ("ctimetest.1", ))
@@ -182,6 +184,7 @@ def testListdir(self):
         self.assertEqual(dirlist, [b"1", b"2", b"3", b"4"])
 
 
+@unittest.skipIf(os.name == "nt", "Symlinks not supported under Windows")
 class CheckSyms(RPathTest):
     """Check symlinking and reading"""
 
@@ -258,13 +261,9 @@ def testFileLength(self):
 
 class FilenameOps(RPathTest):
     """Check filename operations"""
-    weirdfilename = eval(
-        '\'\\xd8\\xab\\xb1Wb\\xae\\xc5]\\x8a\\xbb\\x15v*\\xf4\\x0f!\\xf9>\\xe2Y\\x86\\xbb\\xab\\xdbp\\xb0\\x84\\x13k\\x1d\\xc2\\xf1\\xf5e\\xa5U\\x82\\x9aUV\\xa0\\xf4\\xdf4\\xba\\xfdX\\x03\\x82\\x07s\\xce\\x9e\\x8b\\xb34\\x04\\x9f\\x17 \\xf4\\x8f\\xa6\\xfa\\x97\\xab\\xd8\\xac\\xda\\x85\\xdcKvC\\xfa#\\x94\\x92\\x9e\\xc9\\xb7\\xc3_\\x0f\\x84g\\x9aB\\x11<=^\\xdbM\\x13\\x96c\\x8b\\xa7|*"\\\\\\\'^$@#!(){}?+ ~` \''
-    )
     normdict = {
         b"/": b"/",
         b".": b".",
-        b"//": b"/",
         b"/a/b": b"/a/b",
         b"a/b": b"a/b",
         b"a//b": b"a/b",
@@ -275,6 +274,8 @@ class FilenameOps(RPathTest):
         b"//host/share": b"//host/share",
         b"//host//share/": b"//host/share",
     }
+    if os.name != "nt":  # Windows doesn't like double slashes
+        normdict[b"//"] = b"/"
     dirsplitdict = {
         b"/": (b"", b""),
         b"/a": (b"", b"a"),
@@ -285,17 +286,6 @@ class FilenameOps(RPathTest):
         b"//host/share": (b"//host", b"share"),
     }
 
-    def testQuote(self):
-        """See if filename quoting works"""
-        wtf = rpath.RPath(self.lc, self.prefix, (self.weirdfilename, ))
-        reg = rpath.RPath(self.lc, self.prefix, (b"regular_file", ))
-        self.assertTrue(wtf.lstat())
-        self.assertTrue(reg.lstat())
-        self.assertEqual(os_system(
-            b"ls %s >/dev/null 2>&1" % wtf.quote()), 0)
-        self.assertEqual(os_system(
-            b"ls %s >/dev/null 2>&1" % reg.quote()), 0)
-
     def testNormalize(self):
         """rpath.normalize() dictionary test"""
         for (before, after) in list(self.normdict.items()):
@@ -357,7 +347,12 @@ def testGzipWrite(self):
 
         with rp_gz.open("wb", compress=1) as fp_out:
             fp_out.write(s)
-        self.assertEqual(os_system(b"gunzip %s" % file_gz), 0)
+        if os.name == "nt":
+            self.assertEqual(
+                os_system(b"7z x -o%s %s >NUL" % (abs_output_dir, file_gz)), 0)
+            os_system(b"del %s" % file_gz)
+        else:
+            self.assertEqual(os_system(b"gunzip %s" % file_gz), 0)
         with rp_nogz.open("rb") as fp_in:
             self.assertEqual(fp_in.read(), s)
 
@@ -380,7 +375,13 @@ def testGzipRead(self):
         rp_nogz.setdata()
         self.assertTrue(rp_nogz.lstat())
 
-        self.assertEqual(os_system(b"gzip %s" % file_nogz), 0)
+        if os.name == "nt":
+            self.assertEqual(
+                os_system(b"7z a -tgzip -sdel %s %s >NUL" % (file_gz,
+                                                             file_nogz)),
+                0)
+        else:
+            self.assertEqual(os_system(b"gzip %s" % file_nogz), 0)
         rp_nogz.setdata()
         rp_gz.setdata()
         self.assertFalse(rp_nogz.lstat())
@@ -411,6 +412,7 @@ def testComp(self):
         self.assertFalse(rpath.cmp(self.rf, self.hl1))
         self.assertFalse(rpath.cmp(self.dir, self.rf))
 
+    @unittest.skipIf(os.name == "nt", "Symlinks not supported under Windows")
     def testCompMisc(self):
         """Test miscellaneous comparisons"""
         self.assertTrue(
@@ -434,7 +436,11 @@ def testDirSizeComp(self):
 
     def testCopy(self):
         """Test copy of various files"""
-        for rp in [self.sl, self.rf, self.fifo, self.dir]:
+        if os.name == "nt":
+            comp_list = [self.rf, self.dir]
+        else:
+            comp_list = [self.sl, self.rf, self.fifo, self.dir]
+        for rp in comp_list:
             rpath.copy(rp, self.dest)
             self.assertTrue(self.dest.lstat())
             self.assertTrue(rpath.cmp(rp, self.dest))
@@ -496,10 +502,11 @@ def testCopyWithAttribs(self):
         out = rpath.RPath(self.lc, self.write_dir, ("out", ))
         if out.lstat():
             out.delete()
-        for rp in [
-                self.noperms, self.nowrite, self.rf, self.exec1, self.exec2,
-                self.hl1, self.dir, self.sym
-        ]:
+        copy_list = [self.noperms, self.nowrite, self.rf,
+                     self.exec1, self.exec2, self.hl1, self.dir]
+        if os.name != "nt":  # symlinks not supported under Windows
+            copy_list.append(self.sym)
+        for rp in copy_list:
             rpath.copy_with_attribs(rp, out)
             self.assertTrue(rpath.cmp(rp, out))
             self.assertTrue(rp.equal_loose(out))