fix some versioning related bugs and add tests

Fri, 15 Nov 2019 18:07:11 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Fri, 15 Nov 2019 18:07:11 +0100
changeset 679
0d352b79363a
parent 678
41b4cc024249
child 680
e66f2645be65

fix some versioning related bugs and add tests

dav/scfg.c file | annotate | diff | comparison | revisions
dav/sync.c file | annotate | diff | comparison | revisions
dav/sync.h file | annotate | diff | comparison | revisions
test/bin-test/dav-home/sync.xml file | annotate | diff | comparison | revisions
test/bin-test/test-dav-sync-versioning1.sh file | annotate | diff | comparison | revisions
test/bin-test/test-dav-sync.sh file | annotate | diff | comparison | revisions
--- a/dav/scfg.c	Sat Nov 09 10:18:58 2019 +0100
+++ b/dav/scfg.c	Fri Nov 15 18:07:11 2019 +0100
@@ -452,10 +452,8 @@
         if(node->type == XML_ELEMENT_NODE) {
             char *value = util_xml_get_text(node);
             /* every key needs a value */
-            if(!value) {
-                /* TODO: maybe this should only be reported, if the key is valid
-                 * But this makes the code very ugly.
-                 */
+            if(!value && !xstreq(node->name, "versioning")) {
+                // TODO: only report if value is required
                 print_error(node->line,
                         "missing value for directory element: %s\n",
                         node->name);
--- a/dav/sync.c	Sat Nov 09 10:18:58 2019 +0100
+++ b/dav/sync.c	Fri Nov 15 18:07:11 2019 +0100
@@ -839,7 +839,7 @@
         }
         
         // download the resource
-        if(sync_get_resource(a, dir, res->path, res, db, &sync_success)) {
+        if(sync_get_resource(a, dir, res->path, res, db, TRUE, &sync_success)) {
             fprintf(stderr, "resource download failed: %s\n", res->path);
             sync_error++;
         }
@@ -1368,6 +1368,7 @@
         const char *path,
         DavResource *res,
         SyncDatabase *db,
+        DavBool update_db,
         int *counter)
 { 
     char *link = SYNC_SYMLINK(dir) ?
@@ -1488,7 +1489,14 @@
                 return -1;
             }
         }
-
+        
+    } else if(tmp_path) {
+        if(sys_unlink(tmp_path)) {
+            fprintf(stderr, "Cannot remove tmp file: %s\n", tmp_path);
+        }
+    }
+    
+    if(update_db && ret == 0) {
         if(!local) {
             // new local resource
             local = calloc(1, sizeof(LocalResource));
@@ -1535,10 +1543,6 @@
         }
         sync_set_metadata_from_stat(local, &s);
         local->skipped = FALSE;
-    } else if(tmp_path) {
-        if(sys_unlink(tmp_path)) {
-            fprintf(stderr, "Cannot remove tmp file: %s\n", tmp_path);
-        }
     }
     
     if(tmp_path) {
@@ -2506,6 +2510,7 @@
         }
         
         DavResource *vres = NULL;
+        DavBool update_local_entry = TRUE;
         if(version) {
             if(dir->versioning->type == VERSIONING_SIMPLE) {
                 vres = versioning_simple_find(res, version);
@@ -2517,6 +2522,12 @@
                 ret = 1;
                 break;
             }
+            
+            // By restoring an old version of a file, the local dir is not
+            // in sync with the server anymore. Mark this file to change
+            // the metadata later, to make sure, the file will be detected
+            // as locally modified, on the next push/pull
+            update_local_entry = FALSE;
         } else {
             vres = res;
         }
@@ -2532,9 +2543,16 @@
                 }
                 free(local_path);
             } else {
-                if(sync_get_resource(a, dir, res->path, vres, db, &sync_success)) {
+                if(sync_get_resource(a, dir, res->path, vres, db, update_local_entry, &sync_success)) {
                     fprintf(stderr, "sync_get_resource failed for resource: %s\n", res->path);
                     sync_error++;
+                } else if(!update_local_entry) {
+                    LocalResource *lr = ucx_map_cstr_get(db->resources, res->path);
+                    if(lr) {
+                        lr->last_modified = 0;
+                        nullfree(lr->hash);
+                        lr->hash = NULL;
+                    } // else should not happen
                 }
             }
         }
@@ -3673,10 +3691,17 @@
     }
 }
 
-int versioning_delete(SyncDirectory *dir, DavResource *res) {
+int versioning_delete_begin(SyncDirectory *dir, DavResource *res, int *exists) {
     if(dir->versioning->type == VERSIONING_SIMPLE) {
-        // TODO
-    }
+        versioning_begin(dir, res, exists);
+    } else {
+        // versioning delete with DeltaV currently not supported in dav-sync
+        *exists = 1;
+    }
+    return 0;
+}
+
+int versioning_delete_end(SyncDirectory *dir, DavResource *res) {
     return 0;
 }
 
@@ -4213,17 +4238,20 @@
             // local resource metadata == remote resource metadata
             // resource can be deleted
             printf("delete: %s\n", res->path);
-            
+            int exists = 1;
+            int vend_required = 0;
             if(dir->versioning && dir->versioning->always) {
-                if(versioning_delete(dir, res)) {
+                if(versioning_delete_begin(dir, res, &exists)) {
                     fprintf(
                             stderr,
                             "Cannot save resource version before deletion\n");
                     ret = 1;
+                } else {
+                    vend_required = 1;
                 }
             }
             
-            if(!ret && dav_delete(res)) {
+            if(!ret && dav_delete(res) && exists) {
                 if(sn->error != DAV_NOT_FOUND) {
                     fprintf(stderr, "Cannot delete resource %s\n", res->path);
                     ret = 1;
@@ -4231,6 +4259,10 @@
             } else {
                 (*counter)++;
             }
+            
+            if(vend_required) {
+                versioning_delete_end(dir, res);
+            }
         }
         // else TODO: should we inform the user that the file was modified on
         // the server and delete was skipped?
@@ -5362,7 +5394,10 @@
             return hex_hash;
         }
     } else {
-        return dav_get_string_property_ns(res, DAV_NS, "content-hash");
+        char *hash = dav_get_string_property_ns(res, DAV_NS, "content-hash");
+        if(hash) {
+            return strdup(hash);
+        }
     }
     return NULL;
 }
--- a/dav/sync.h	Sat Nov 09 10:18:58 2019 +0100
+++ b/dav/sync.h	Fri Nov 15 18:07:11 2019 +0100
@@ -125,6 +125,7 @@
         const char *path,
         DavResource *res,
         SyncDatabase *db,
+        DavBool update_local,
         int *counter);
 int sync_get_collection(
         CmdArgs *a,
--- a/test/bin-test/dav-home/sync.xml	Sat Nov 09 10:18:58 2019 +0100
+++ b/test/bin-test/dav-home/sync.xml	Fri Nov 15 18:07:11 2019 +0100
@@ -183,4 +183,33 @@
 		</tagconfig>
 		<metadata>all</metadata>
 	</directory>
+	
+	<!--
+	Test 5: Versioning
+	-->
+	<directory>
+		<name>test5a</name>
+		<path>$HOME/tmp-sync/test5a</path>
+		<repository>dav-test-repo</repository>
+		<collection>/sync/test5</collection>
+		<trash>.trash</trash>
+		<database>dav-sync-tests-test5a-db.xml</database>
+		
+		<hashing>true</hashing>
+		
+		<versioning type="simple" always="true" />
+	</directory>
+	
+	<directory>
+		<name>test5b</name>
+		<path>$HOME/tmp-sync/test5b</path>
+		<repository>dav-test-repo</repository>
+		<collection>/sync/test5</collection>
+		<trash>.trash</trash>
+		<database>dav-sync-tests-test5b-db.xml</database>
+		
+		<hashing>true</hashing>
+		
+		<versioning type="simple" always="true" />
+	</directory>
 </configuration>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/bin-test/test-dav-sync-versioning1.sh	Fri Nov 15 18:07:11 2019 +0100
@@ -0,0 +1,304 @@
+#!/bin/sh
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2019 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#   1. Redistributions of source code must retain the above copyright
+#      notice, this list of conditions and the following disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above copyright
+#      notice, this list of conditions and the following disclaimer in the
+#      documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+if [ -z "$DAV_BIN" ];
+then
+	echo "DAV_BIN variable not set"
+	exit 1
+fi
+if [ -z "$DAV_SYNC_BIN" ];
+then
+	echo "DAV_BIN variable not set"
+	exit 1
+fi
+
+XATTR=../../build/xattrtool
+
+# checks if tmp-sync/out.txt contains a specific text
+# arg1: pattern
+# arg2: errormsg
+check_tmpout()
+{
+	TEST=`cat tmp-sync/out.txt | grep "$1"`
+	if [ $? -ne 0 ];
+	then
+		echo "$2"
+		exit 2	
+	fi
+}
+
+# checks if tmp-sync/out.txt does not contain a specific text
+# arg1: pattern
+# arg2: errormsg
+ncheck_tmpout()
+{
+	TEST=`cat tmp-sync/out.txt | grep "$1"`
+	if [ $? -eq 0 ];
+	then
+		echo "$2"
+		exit 2	
+	fi
+}
+
+# do dav-sync push and check return value
+# arg1: dir
+# arg2: errormsg
+dav_sync_push()
+{
+	$DAV_SYNC_BIN push $1 > tmp-sync/out.txt
+	if [ $? -ne 0 ];
+	then
+		echo "$2"
+		exit 2
+	fi
+}
+# do dav-sync pull and check return value
+# arg1: dir
+# arg2: errormsg
+dav_sync_pull()
+{
+	$DAV_SYNC_BIN pull $1 > tmp-sync/out.txt
+	if [ $? -ne 0 ];
+	then
+		echo "$2"
+		exit 2
+	fi
+}
+
+rm -f .dav/dav-sync-tests-test5a-db.xml
+rm -f .dav/dav-sync-tests-test5b-db.xml
+
+$DAV_BIN rm dav-test-repo/sync/test5 2> /dev/null
+
+$DAV_BIN mkcol dav-test-repo/sync/test5 2> /dev/null
+
+# tmp sync dir
+rm -Rf tmp-sync
+mkdir tmp-sync
+mkdir tmp-sync/test5a
+mkdir tmp-sync/test5b
+
+# ----------------------------------------------------------------------------
+# test 1: add some files (preparation, make sure everything works)
+# expected result: everything synced
+
+mkdir tmp-sync/test5a/dir1
+mkdir tmp-sync/test5a/dir1/sub1
+
+cp synctest/file1 tmp-sync/test5a
+cp synctest/file2 tmp-sync/test5a/dir1
+cp synctest/file3 tmp-sync/test5a/dir1/sub1
+
+dav_sync_push test5a "test 1: push failed"
+check_tmpout "3 files pushed" "test 1: wrong push counter"
+check_tmpout "0 conflicts" "test 1: wrong conflict counter (push)"
+check_tmpout "0 errors" "test 1: wrong error counter (push)"
+
+dav_sync_pull test5b "test 1: pull failed"
+check_tmpout "3 files pulled" "test 1: wrong pull counter"
+check_tmpout "0 conflicts" "test 1: wrong conflict counter (pull)"
+check_tmpout "0 errors" "test 1: wrong error counter (pull)"
+
+
+# ----------------------------------------------------------------------------
+# test 2: modify file, sync
+# expected result: old resource moved to history collection
+
+echo "test2-mod1" >> tmp-sync/test5a/file1
+touch -t 01011200 tmp-sync/test5a/file1
+
+dav_sync_push test5a "test 2: push failed"
+check_tmpout "1 file pushed" "test 2: wrong push counter"
+check_tmpout "0 conflicts" "test 2: wrong conflict counter (push)"
+check_tmpout "0 errors" "test 2: wrong error counter (push)"
+
+dav_sync_pull test5b "test 2: pull failed"
+check_tmpout "1 file pulled" "test 2: wrong pull counter"
+check_tmpout "0 conflicts" "test 2: wrong conflict counter (pull)"
+check_tmpout "0 errors" "test 2: wrong error counter (pull)"
+
+dav list -R dav-test-repo/sync/test5/.dav-version-history/ > tmp-sync/out.txt 2> /dev/null
+if [ $? -ne 0 ]; then
+	echo "test 2: dav list failed"
+	exit 2
+fi
+
+TEST=`cat tmp-sync/out.txt | wc -l 2> /dev/null`
+if [ -z "$TEST" ]; then
+	echo "test 2: wc failed"
+	exit 2
+fi
+
+# line count is 1 (sub-history-collection) + 1 file version = 2
+if [ $TEST -ne 2 ]; then
+	echo "test 2: wrong line count"
+	exit 2
+fi
+
+
+# ----------------------------------------------------------------------------
+# test 3: modify file again, sync
+# expected result: old resource moved to history collection, 2 old versions
+
+echo "test3-mod1" >> tmp-sync/test5a/file1
+touch -t 02011200 tmp-sync/test5a/file1
+
+dav_sync_push test5a "test 3: push failed"
+check_tmpout "1 file pushed" "test 3: wrong push counter"
+check_tmpout "0 conflicts" "test 3: wrong conflict counter (push)"
+check_tmpout "0 errors" "test 3: wrong error counter (push)"
+
+dav_sync_pull test5b "test 3: pull failed"
+check_tmpout "1 file pulled" "test 3: wrong pull counter"
+check_tmpout "0 conflicts" "test 3: wrong conflict counter (pull)"
+check_tmpout "0 errors" "test 3: wrong error counter (pull)"
+
+dav list -R dav-test-repo/sync/test5/.dav-version-history/ > tmp-sync/out.txt 2> /dev/null
+TEST=`cat tmp-sync/out.txt | wc -l 2> /dev/null`
+if [ $TEST -ne 3 ]; then
+	echo "test 3: wrong line count"
+	exit 2
+fi
+
+
+# ----------------------------------------------------------------------------
+# test 4: modify file2, add new file, sync
+# expected result: file2 versionized, new file just uploaded
+
+echo "test4-new1" > tmp-sync/test5a/new1
+echo "test4-mod1" >> tmp-sync/test5a/dir1/file2
+
+dav_sync_push test5a "test 4: push failed"
+check_tmpout "2 files pushed" "test 4: wrong push counter"
+check_tmpout "0 conflicts" "test 4: wrong conflict counter (push)"
+check_tmpout "0 errors" "test 4: wrong error counter (push)"
+
+dav_sync_pull test5b "test 4: pull failed"
+check_tmpout "2 files pulled" "test 4: wrong pull counter"
+check_tmpout "0 conflicts" "test 4: wrong conflict counter (pull)"
+check_tmpout "0 errors" "test 4: wrong error counter (pull)"
+
+dav list -R dav-test-repo/sync/test5/.dav-version-history/ > tmp-sync/out.txt 2> /dev/null
+TEST=`cat tmp-sync/out.txt | wc -l 2> /dev/null`
+
+# line count is 3 (prev) + 1 new sub-history-collection + 1 file version = 5
+if [ $TEST -ne 5 ]; then
+	echo "test 4: wrong line count"
+	exit 2
+fi
+
+
+# ----------------------------------------------------------------------------
+# test 5: restore previous file2 version
+# expected result: file restored
+
+VERSION=`$DAV_SYNC_BIN list-versions tmp-sync/test5a/dir1/file2 | grep "name: " 2> /dev/null`
+if [ $? -ne 0 ]; then
+	echo "test 5: list-versions failed"
+	exit 2
+fi
+
+# extract version name
+VERSION=${VERSION:5}
+
+$DAV_SYNC_BIN restore -V $VERSION tmp-sync/test5a/dir1/file2 > tmp-sync/out.txt 2> /dev/null
+if [ $? -ne 0 ]; then
+	echo "test 5: restore failed"
+	exit 2
+fi
+check_tmpout "1 file pulled" "test 5: wrong pull counter"
+
+# compare restored file with base file
+diff synctest/file2 tmp-sync/test5a/dir1/file2 > /dev/null 2>&1
+
+if [ $? -ne 0 ]; then
+	echo "test 5: wrong file2 content"
+	exit 2
+fi
+
+
+# ----------------------------------------------------------------------------
+# test 6: push test5a
+# expected result: file2 pushed
+
+dav_sync_push test5a "test 6: push failed"
+check_tmpout "1 file pushed" "test 6: wrong push counter"
+check_tmpout "file2" "test 6: file2 not pushed"
+check_tmpout "0 conflicts" "test 6: wrong conflict counter (push)"
+check_tmpout "0 errors" "test 6: wrong error counter (push)"
+
+dav_sync_pull test5b "test 6: pull failed"
+check_tmpout "1 file pulled" "test 6: wrong pull counter"
+check_tmpout "0 conflicts" "test 6: wrong conflict counter (pull)"
+check_tmpout "0 errors" "test 6: wrong error counter (pull)"
+
+
+# ----------------------------------------------------------------------------
+# test 7: modify file2 again and push (this is just a prep for test8)
+# expected result: file2 pushed
+
+sleep 2
+
+echo "test7-mod1" >> tmp-sync/test5a/dir1/file2
+
+dav_sync_push test5a "test 7: push failed"
+check_tmpout "1 file pushed" "test 7: wrong push counter"
+check_tmpout "0 conflicts" "test 7: wrong conflict counter (push)"
+check_tmpout "0 errors" "test 7: wrong error counter (push)"
+
+dav_sync_pull test5b "test 7: pull failed"
+check_tmpout "1 file pulled" "test 7: wrong pull counter"
+check_tmpout "0 conflicts" "test 7: wrong conflict counter (pull)"
+check_tmpout "0 errors" "test 7: wrong error counter (pull)"
+
+
+# ----------------------------------------------------------------------------
+# test 8: restore previous file2 version and pull
+# expected result: file restored, no files pulled
+
+$DAV_SYNC_BIN restore -V $VERSION tmp-sync/test5a/dir1/file2 > tmp-sync/out.txt 2> /dev/null
+if [ $? -ne 0 ]; then
+	echo "test 8: restore failed"
+	exit 2
+fi
+check_tmpout "1 file pulled" "test 8: wrong pull counter (restore)"
+
+# compare restored file with base file
+diff synctest/file2 tmp-sync/test5a/dir1/file2 > /dev/null 2>&1
+
+if [ $? -ne 0 ]; then
+	echo "test 8: wrong file2 content"
+	exit 2
+fi
+
+dav_sync_pull test5a "test 8: pull failed"
+check_tmpout "0 files pulled" "test 8: wrong pull counter"
+check_tmpout "0 conflicts" "test 8: wrong conflict counter (pull)"
+check_tmpout "0 errors" "test 8: wrong error counter (pull)"
+
--- a/test/bin-test/test-dav-sync.sh	Sat Nov 09 10:18:58 2019 +0100
+++ b/test/bin-test/test-dav-sync.sh	Fri Nov 15 18:07:11 2019 +0100
@@ -66,7 +66,7 @@
 
 # check if config works
 TEST1_DIR=`$DAV_SYNC_BIN list-dirs | grep test1a | tail -1`
-if [ -z $TEST1_DIR ];
+if [ -z "$TEST1_DIR" ];
 then
 	echo "Config not working"
 	rm -Rf .dav
@@ -105,6 +105,7 @@
 do_test "dav-sync metadata (2)" test-dav-sync-metadata2.sh
 do_test "dav-sync metadata (3)" test-dav-sync-metadata3.sh
 do_test "dav-sync metadata (4)" test-dav-sync-metadata4.sh
+do_test "dav-sync versioning (1)" test-dav-sync-versioning1.sh
 
 # cleanup
 $DAV_BIN rm dav-test-repo/sync/test1 > /dev/null 2>&1

mercurial