From 24120d8b8b45d8810fcf27de3ad6daec9e32d4f6 Mon Sep 17 00:00:00 2001 From: Kenneth Hsu Date: Mon, 16 Aug 2021 12:58:54 -0700 Subject: [PATCH 1/4] Add tests for restoring without local data. --- Duplicati/UnitTest/RestoreHandlerTests.cs | 38 +++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/Duplicati/UnitTest/RestoreHandlerTests.cs b/Duplicati/UnitTest/RestoreHandlerTests.cs index cba8b6aa8..06c281fb9 100644 --- a/Duplicati/UnitTest/RestoreHandlerTests.cs +++ b/Duplicati/UnitTest/RestoreHandlerTests.cs @@ -119,5 +119,43 @@ namespace Duplicati.UnitTest Assert.IsTrue(restoredFileSecurity.AreAccessRulesProtected); } } + + [Test] + [Category("RestoreHandler")] + public void RestoreWithoutLocalData([Values("true", "false")] string noLocalDb, [Values("true", "false")] string noLocalBlocks, [Values("true", "false")] string patchWithLocalBlocks) + { + string file1Path = Path.Combine(this.DATAFOLDER, "file1"); + File.WriteAllBytes(file1Path, new byte[] { 1, 2, 3 }); + + string file2Path = Path.Combine(this.DATAFOLDER, "file2"); + File.WriteAllBytes(file2Path, new byte[] { 3, 4, 6 }); + + string folderPath = Path.Combine(this.DATAFOLDER, "folder"); + Directory.CreateDirectory(folderPath); + systemIO.FileCopy(file1Path, Path.Combine(folderPath, "file1 copy"), true); + + using (Controller c = new Controller("file://" + this.TARGETFOLDER, new Dictionary(this.TestOptions), null)) + { + IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); + Assert.AreEqual(0, backupResults.Errors.Count()); + Assert.AreEqual(0, backupResults.Warnings.Count()); + } + + Dictionary restoreOptions = new Dictionary(this.TestOptions) + { + ["restore-path"] = this.RESTOREFOLDER, + ["no-local-db"] = noLocalDb, + ["no-local-blocks"] = noLocalBlocks, + ["patch-with-local-blocks"] = patchWithLocalBlocks + }; + using (Controller c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) + { + IRestoreResults restoreResults = c.Restore(new[] { "*" }); + Assert.AreEqual(0, restoreResults.Errors.Count()); + Assert.AreEqual(0, restoreResults.Warnings.Count()); + } + + TestUtils.AssertDirectoryTreesAreEquivalent(this.DATAFOLDER, this.RESTOREFOLDER, true, "Restoring without local data"); + } } } \ No newline at end of file From c30d28318867ae8d5597b38bd9beb4b31f7df3cc Mon Sep 17 00:00:00 2001 From: Jojo-1000 <33495614+Jojo-1000@users.noreply.github.com> Date: Wed, 11 Oct 2023 23:30:02 +0200 Subject: [PATCH 2/4] Fix restore with blocks from different files. Closes #4593 The logic for finding existing source blocks requires that the same target files and hashes are adjacent in query results, but this was not the case. --- Duplicati/Library/Main/Database/LocalRestoreDatabase.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Duplicati/Library/Main/Database/LocalRestoreDatabase.cs b/Duplicati/Library/Main/Database/LocalRestoreDatabase.cs index c0daecc54..00a68a6b6 100644 --- a/Duplicati/Library/Main/Database/LocalRestoreDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalRestoreDatabase.cs @@ -680,8 +680,9 @@ namespace Duplicati.Library.Main.Database using(var cmd = connection.CreateCommand()) { // TODO: Skip metadata as required - cmd.CommandText = string.Format(@"SELECT DISTINCT ""A"".""TargetPath"", ""A"".""ID"", ""B"".""Hash"", (""B"".""Index"" * {2}), ""B"".""Index"", ""B"".""Size"", ""C"".""Path"", (""D"".""Index"" * {2}), ""E"".""Size"", ""B"".""Metadata"" FROM ""{0}"" ""A"", ""{1}"" ""B"", ""File"" ""C"", ""BlocksetEntry"" ""D"", ""Block"" E WHERE ""A"".""ID"" = ""B"".""FileID"" AND ""C"".""BlocksetID"" = ""D"".""BlocksetID"" AND ""D"".""BlockID"" = ""E"".""ID"" AND ""B"".""Hash"" = ""E"".""Hash"" AND ""B"".""Size"" = ""E"".""Size"" AND ""B"".""Restored"" = 0", filetablename, blocktablename, blocksize); - using(var rd = cmd.ExecuteReader()) + // Have to order by target path and hash, to ensure BlockDescriptor and BlockSource match adjacent rows + cmd.CommandText = string.Format(@"SELECT DISTINCT ""A"".""TargetPath"", ""A"".""ID"", ""B"".""Hash"", (""B"".""Index"" * {2}), ""B"".""Index"", ""B"".""Size"", ""C"".""Path"", (""D"".""Index"" * {2}), ""E"".""Size"", ""B"".""Metadata"" FROM ""{0}"" ""A"", ""{1}"" ""B"", ""File"" ""C"", ""BlocksetEntry"" ""D"", ""Block"" E WHERE ""A"".""ID"" = ""B"".""FileID"" AND ""C"".""BlocksetID"" = ""D"".""BlocksetID"" AND ""D"".""BlockID"" = ""E"".""ID"" AND ""B"".""Hash"" = ""E"".""Hash"" AND ""B"".""Size"" = ""E"".""Size"" AND ""B"".""Restored"" = 0 ORDER BY ""A"".""TargetPath"", ""B"".""Index""", filetablename, blocktablename, blocksize); + using (var rd = cmd.ExecuteReader()) { if (rd.Read()) { From e8e112ffdb13e1e6e770b548657afca1253c0ec7 Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Mon, 12 May 2025 13:05:26 +0200 Subject: [PATCH 3/4] Fixed tests --- Duplicati/UnitTest/RestoreHandlerTests.cs | 26 ++++++++--------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/Duplicati/UnitTest/RestoreHandlerTests.cs b/Duplicati/UnitTest/RestoreHandlerTests.cs index d1b52e1dd..4142924d4 100644 --- a/Duplicati/UnitTest/RestoreHandlerTests.cs +++ b/Duplicati/UnitTest/RestoreHandlerTests.cs @@ -171,38 +171,30 @@ namespace Duplicati.UnitTest [Test] [Category("RestoreHandler")] - public void RestoreWithoutLocalData([Values("true", "false")] string noLocalDb, [Values("true", "false")] string noLocalBlocks, [Values("true", "false")] string patchWithLocalBlocks) + public void RestoreWithoutLocalData([Values("true", "false")] string noLocalDb, [Values("true", "false")] string patchWithLocalBlocks) { - string file1Path = Path.Combine(this.DATAFOLDER, "file1"); + var file1Path = Path.Combine(this.DATAFOLDER, "file1"); File.WriteAllBytes(file1Path, new byte[] { 1, 2, 3 }); - string file2Path = Path.Combine(this.DATAFOLDER, "file2"); + var file2Path = Path.Combine(this.DATAFOLDER, "file2"); File.WriteAllBytes(file2Path, new byte[] { 3, 4, 6 }); - string folderPath = Path.Combine(this.DATAFOLDER, "folder"); + var folderPath = Path.Combine(this.DATAFOLDER, "folder"); Directory.CreateDirectory(folderPath); systemIO.FileCopy(file1Path, Path.Combine(folderPath, "file1 copy"), true); using (Controller c = new Controller("file://" + this.TARGETFOLDER, new Dictionary(this.TestOptions), null)) - { - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); - Assert.AreEqual(0, backupResults.Errors.Count()); - Assert.AreEqual(0, backupResults.Warnings.Count()); - } + TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); - Dictionary restoreOptions = new Dictionary(this.TestOptions) + var restoreOptions = new Dictionary(this.TestOptions) { ["restore-path"] = this.RESTOREFOLDER, ["no-local-db"] = noLocalDb, - ["no-local-blocks"] = noLocalBlocks, - ["patch-with-local-blocks"] = patchWithLocalBlocks + ["restore-with-local-blocks"] = patchWithLocalBlocks }; + using (Controller c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) - { - IRestoreResults restoreResults = c.Restore(new[] { "*" }); - Assert.AreEqual(0, restoreResults.Errors.Count()); - Assert.AreEqual(0, restoreResults.Warnings.Count()); - } + TestUtils.AssertResults(c.Restore(new[] { "*" })); TestUtils.AssertDirectoryTreesAreEquivalent(this.DATAFOLDER, this.RESTOREFOLDER, true, "Restoring without local data"); } From e069e504f6fa3fda3396d65f3667d9854fb74240 Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Mon, 12 May 2025 13:07:53 +0200 Subject: [PATCH 4/4] Restored missing test --- Duplicati/UnitTest/RestoreHandlerTests.cs | 45 +++++++++-------------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/Duplicati/UnitTest/RestoreHandlerTests.cs b/Duplicati/UnitTest/RestoreHandlerTests.cs index 4142924d4..f3bb198ae 100644 --- a/Duplicati/UnitTest/RestoreHandlerTests.cs +++ b/Duplicati/UnitTest/RestoreHandlerTests.cs @@ -113,37 +113,31 @@ namespace Duplicati.UnitTest { return; } - /* TODO-DNC - string folderPath = Path.Combine(this.DATAFOLDER, "folder"); + + var folderPath = Path.Combine(this.DATAFOLDER, "folder"); Directory.CreateDirectory(folderPath); - string filePath = Path.Combine(folderPath, "file"); - File.WriteAllBytes(filePath, new byte[] {0}); + var filePath = Path.Combine(folderPath, "file"); + File.WriteAllBytes(filePath, new byte[] { 0 }); // Protect access rules on the file. - FileSecurity fileSecurity = new FileInfo(filePath).GetAccessControl(); + var fileSecurity = new FileInfo(filePath).GetAccessControl(); fileSecurity.SetAccessRuleProtection(true, true); new FileInfo(filePath).SetAccessControl(fileSecurity); - Dictionary options = new Dictionary(this.TestOptions); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) - { - IBackupResults backupResults = c.Backup(new[] {this.DATAFOLDER}); - Assert.AreEqual(0, backupResults.Errors.Count()); - Assert.AreEqual(0, backupResults.Warnings.Count()); - } + var options = new Dictionary(this.TestOptions); + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) + TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); // First, restore without restoring permissions. - Dictionary restoreOptions = new Dictionary(this.TestOptions) {["restore-path"] = this.RESTOREFOLDER}; - using (Controller c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) + var restoreOptions = new Dictionary(this.TestOptions) { ["restore-path"] = this.RESTOREFOLDER }; + using (var c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) { - IRestoreResults restoreResults = c.Restore(new[] {filePath}); - Assert.AreEqual(0, restoreResults.Errors.Count()); - Assert.AreEqual(0, restoreResults.Warnings.Count()); + TestUtils.AssertResults(c.Restore(new[] { filePath })); - string restoredFilePath = Path.Combine(this.RESTOREFOLDER, "file"); + var restoredFilePath = Path.Combine(this.RESTOREFOLDER, "file"); Assert.IsTrue(File.Exists(restoredFilePath)); - FileSecurity restoredFileSecurity = new FileInfo(restoredFilePath).GetAccessControl(); + var restoredFileSecurity = new FileInfo(restoredFilePath).GetAccessControl(); Assert.IsFalse(restoredFileSecurity.AreAccessRulesProtected); // Remove the restored file so that the later restore avoids the "Restore completed @@ -154,19 +148,16 @@ namespace Duplicati.UnitTest // Restore with restoring permissions. restoreOptions["overwrite"] = "true"; restoreOptions["restore-permissions"] = "true"; - using (Controller c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) { - IRestoreResults restoreResults = c.Restore(new[] {filePath}); - Assert.AreEqual(0, restoreResults.Errors.Count()); - Assert.AreEqual(0, restoreResults.Warnings.Count()); + TestUtils.AssertResults(c.Restore(new[] { filePath })); - string restoredFilePath = Path.Combine(this.RESTOREFOLDER, "file"); + var restoredFilePath = Path.Combine(this.RESTOREFOLDER, "file"); Assert.IsTrue(File.Exists(restoredFilePath)); - FileSecurity restoredFileSecurity = new FileInfo(restoredFilePath).GetAccessControl(); + var restoredFileSecurity = new FileInfo(restoredFilePath).GetAccessControl(); Assert.IsTrue(restoredFileSecurity.AreAccessRulesProtected); } - */ } [Test] @@ -193,7 +184,7 @@ namespace Duplicati.UnitTest ["restore-with-local-blocks"] = patchWithLocalBlocks }; - using (Controller c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) TestUtils.AssertResults(c.Restore(new[] { "*" })); TestUtils.AssertDirectoryTreesAreEquivalent(this.DATAFOLDER, this.RESTOREFOLDER, true, "Restoring without local data");