From 3c62fb7e39e4134b386445dbfcfdcab6e7286450 Mon Sep 17 00:00:00 2001 From: Carl Johnsen Date: Mon, 27 Jan 2025 09:53:48 +0100 Subject: [PATCH] Preliminary rsync tool test implemented. --- Duplicati/UnitTest/ToolTests.cs | 93 ++++++++++++++++++++++++-- Tools/RemoteSynchronization/Program.cs | 2 +- 2 files changed, 88 insertions(+), 7 deletions(-) diff --git a/Duplicati/UnitTest/ToolTests.cs b/Duplicati/UnitTest/ToolTests.cs index a1e48084c..9591a3c55 100644 --- a/Duplicati/UnitTest/ToolTests.cs +++ b/Duplicati/UnitTest/ToolTests.cs @@ -1,16 +1,44 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + using Duplicati.Library.Common.IO; using Duplicati.Library.Main; using NUnit.Framework; using System; using System.Collections.Generic; using System.IO; +using System.Linq; namespace Duplicati.UnitTest { + /// + /// Tests the tools. + /// public class ToolTests : BasicSetupHelper { + /// + /// Tests the remote synchronization tool. + /// [Test] [Category("Tools")] public void TestRemoteSynchronization() @@ -21,9 +49,9 @@ namespace Duplicati.UnitTest var l2 = $"{base_path}/l2"; var l1r = $"{base_path}/l1_restore"; var l2r = $"{base_path}/l2_restore"; - var backup_path = $"{home}/tmp/adaptivecpp"; + var path_to_backup = $"{home}/tmp/adaptivecpp"; - Dictionary options = new () + Dictionary options = new() { ["passphrase"] = "1234" }; @@ -38,28 +66,81 @@ namespace Duplicati.UnitTest // Backup the first level using (var c = new Controller($"file://{l1}", options, null)) { - var results = c.Backup([backup_path]); + var results = c.Backup([path_to_backup]); + Assert.AreEqual(0, results.Errors.Count()); + Assert.AreEqual(0, results.Warnings.Count()); Console.WriteLine($"Backed up {results.AddedFiles} files to {l1}"); } - // TODO Call the remote synchronization tool + // Call the tool var exe = RemoteSynchronization.Program.Main; + var args = new string[] { $"file://{l1}", $"file://{l2}" }; + var async_call = exe(args); + async_call.Wait(); + Assert.AreEqual(0, async_call.Result, "Remote synchronization tool did not return 0."); + // Verify that the directories are equal + Assert.IsTrue(DirectoriesAndContentsAreEqual(l1, l2), "Synchronized directories are not equal"); + + // Try to restore the first level options["restore-path"] = l1r; using (var c = new Controller($"file://{l1}", options, null)) { - var results = c.Restore([Path.Combine(backup_path, "*")]); + var results = c.Restore([Path.Combine(path_to_backup, "*")]); + Assert.AreEqual(0, results.Errors.Count()); + Assert.AreEqual(0, results.Warnings.Count()); Console.WriteLine($"Restored {results.RestoredFiles} files to {options["restore-path"]}"); } + Assert.IsTrue(DirectoriesAndContentsAreEqual(path_to_backup, l1r), "Restored first level files is not equal to original files"); // Try to restore the second level options["restore-path"] = l2r; using (var c = new Controller($"file://{l2}", options, null)) { var results = c.Restore([]); + Assert.AreEqual(0, results.Errors.Count()); + Assert.AreEqual(0, results.Warnings.Count()); Console.WriteLine($"Restored {results.RestoredFiles} files to {options["restore-path"]}"); } + Assert.IsTrue(DirectoriesAndContentsAreEqual(path_to_backup, l2r), "Restored second level files is not equal to original files"); } - } + /// + /// Compares two directories and their contents. + /// + /// The first directory. + /// The second directory. + /// `true` if the two directories contain exactly the same files and if the contents of the files are equivalent. `false` otherwise. + public static bool DirectoriesAndContentsAreEqual(string d1, string d2) + { + // Recursively get the files in the two directories + var f1s = Directory.EnumerateFiles(d1, "*", SearchOption.AllDirectories).Select(x => x[(d1.Length + 1)..]).OrderDescending(); + var f2s = Directory.EnumerateFiles(d2, "*", SearchOption.AllDirectories).Select(x => x[(d2.Length + 1)..]).OrderDescending(); + + // If the two directories do not contain the same files, return false + if (!f1s.SequenceEqual(f2s)) + return false; + + // Check that each file pair exist and have the same content + foreach (var f1 in f1s) + { + var f1full = Path.Combine(d1, f1); + if (!File.Exists(f1full)) + return false; + + var f2full = Path.Combine(d2, f1); + if (!File.Exists(f2full)) + return false; + + var c1 = File.ReadAllText(f1full); + var c2 = File.ReadAllText(f2full); + + if (!c1.SequenceEqual(c2)) + return false; + } + + return true; + } + + } } \ No newline at end of file diff --git a/Tools/RemoteSynchronization/Program.cs b/Tools/RemoteSynchronization/Program.cs index 11f992565..4972352ea 100644 --- a/Tools/RemoteSynchronization/Program.cs +++ b/Tools/RemoteSynchronization/Program.cs @@ -66,7 +66,7 @@ namespace RemoteSynchronization var copied = await CopyAsync(b1s, b2s, to_copy); Console.WriteLine($"Copied {copied} files from {src} to {dst}"); - return 42; + return 0; } // TODO maximum retention?: don't delete files, only rename old ones.