Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/duplicati/duplicati.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDean Ferreyra <dean@octw.com>2020-09-08 06:22:45 +0300
committerDean Ferreyra <dean@octw.com>2020-09-08 08:50:21 +0300
commitbe6ac941ea547c56bc6c46a702ce2abb78b51cad (patch)
tree2a751c71fe3240f5b3c606cf0cfdbaf596b40cf1 /Duplicati/UnitTest
parent40597aaec20bf319d4c02988a9748d81a452ef78 (diff)
Add symlink policy tests
This adds a basic symlink policy test that performs backups using each symlink policy. If you don't have the required privilege in Windows, you can get an exception trying to create a symbolic link. If an exception is thrown trying to create a symbolic link, the exception is trapped and the test is marked as "Ignored"; e.g., ``` 1) Ignored : Duplicati.UnitTest.SymLinkTests.SymLinkPolicy Client could not create a symbolic link. Error reported: (1314) A required privilege is not held by the client. | Read: [C:\td\backup-data\target] | Write: [C:\td\backup-data\symlink] ```
Diffstat (limited to 'Duplicati/UnitTest')
-rw-r--r--Duplicati/UnitTest/BasicSetupHelper.cs12
-rw-r--r--Duplicati/UnitTest/Duplicati.UnitTest.csproj1
-rw-r--r--Duplicati/UnitTest/ProblematicPathTests.cs8
-rwxr-xr-xDuplicati/UnitTest/SymLinkTests.cs157
4 files changed, 170 insertions, 8 deletions
diff --git a/Duplicati/UnitTest/BasicSetupHelper.cs b/Duplicati/UnitTest/BasicSetupHelper.cs
index ba4b3a353..b73abdaa8 100644
--- a/Duplicati/UnitTest/BasicSetupHelper.cs
+++ b/Duplicati/UnitTest/BasicSetupHelper.cs
@@ -21,6 +21,7 @@ using System.Collections.Generic;
using System.IO.Compression;
using Duplicati.Library.Common;
using Duplicati.Library.Common.IO;
+using Duplicati.Library.Utility;
namespace Duplicati.UnitTest
{
@@ -211,6 +212,17 @@ namespace Duplicati.UnitTest
ZipFile.ExtractToDirectory(sourceArchiveFileName, destinationDirectoryName);
}
}
+
+ /// <summary>
+ /// Write file <paramref name="path"/> with <paramref name="contents"/>.
+ /// </summary>
+ protected static void WriteFile(string path, byte[] contents)
+ {
+ using (FileStream fileStream = SystemIO.IO_OS.FileOpenWrite(path))
+ {
+ Utility.CopyStream(new MemoryStream(contents), fileStream);
+ }
+ }
}
}
diff --git a/Duplicati/UnitTest/Duplicati.UnitTest.csproj b/Duplicati/UnitTest/Duplicati.UnitTest.csproj
index 15f3586cd..86b17ef74 100644
--- a/Duplicati/UnitTest/Duplicati.UnitTest.csproj
+++ b/Duplicati/UnitTest/Duplicati.UnitTest.csproj
@@ -44,6 +44,7 @@
<Reference Include="System.IO.Compression.FileSystem" />
</ItemGroup>
<ItemGroup>
+ <Compile Include="SymLinkTests.cs" />
<Compile Include="ControllerTests.cs" />
<Compile Include="DeleteHandlerTests.cs" />
<Compile Include="DisruptionTests.cs" />
diff --git a/Duplicati/UnitTest/ProblematicPathTests.cs b/Duplicati/UnitTest/ProblematicPathTests.cs
index 7d1f05a3a..3e458a779 100644
--- a/Duplicati/UnitTest/ProblematicPathTests.cs
+++ b/Duplicati/UnitTest/ProblematicPathTests.cs
@@ -13,14 +13,6 @@ namespace Duplicati.UnitTest
[TestFixture]
public class ProblematicPathTests : BasicSetupHelper
{
- private static void WriteFile(string path, byte[] contents)
- {
- using (FileStream fileStream = SystemIO.IO_OS.FileOpenWrite(path))
- {
- Utility.CopyStream(new MemoryStream(contents), fileStream);
- }
- }
-
[Test]
[Category("ProblematicPath")]
public void ExcludeProblematicPaths()
diff --git a/Duplicati/UnitTest/SymLinkTests.cs b/Duplicati/UnitTest/SymLinkTests.cs
new file mode 100755
index 000000000..1c6f7bde2
--- /dev/null
+++ b/Duplicati/UnitTest/SymLinkTests.cs
@@ -0,0 +1,157 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Duplicati.Library.Interface;
+using Duplicati.Library.Main;
+using Duplicati.Library.Snapshots;
+using NUnit.Framework;
+
+namespace Duplicati.UnitTest
+{
+ [TestFixture]
+ public class SymLinkTests : BasicSetupHelper
+ {
+ [Test]
+ [Category("SymLink")]
+ public void SymLinkPolicy()
+ {
+ // Create symlink target directory
+ const string targetDirName = "target";
+ var targetDir = systemIO.PathCombine(this.DATAFOLDER, targetDirName);
+ systemIO.DirectoryCreate(targetDir);
+ // Create files in symlink target directory
+ var fileNames = new[] { "a.txt", "b.txt", "c.txt" };
+ foreach (var file in fileNames)
+ {
+ var targetFile = systemIO.PathCombine(targetDir, file);
+ WriteFile(targetFile, Encoding.Default.GetBytes(file));
+ }
+
+ // Create actual symlink directory linking to the target directory
+ const string symlinkDirName = "symlink";
+ var symlinkDir = systemIO.PathCombine(this.DATAFOLDER, symlinkDirName);
+ try
+ {
+ systemIO.CreateSymlink(symlinkDir, targetDir, asDir: true);
+ }
+ catch (Exception e)
+ {
+ // If client cannot create symlinks, mark test as ignored
+ Assert.Ignore($"Client could not create a symbolic link. Error reported: {e.Message}");
+ }
+
+ // Perform backups using all symlink policies and verify restores
+ var symlinkPolicies = new[] { Options.SymlinkStrategy.Store, Options.SymlinkStrategy.Follow, Options.SymlinkStrategy.Ignore };
+ Dictionary<string, string> restoreOptions = new Dictionary<string, string>(this.TestOptions) { ["restore-path"] = this.RESTOREFOLDER };
+ foreach (var symlinkPolicy in symlinkPolicies)
+ {
+ // Backup all files with given symlink policy
+ Dictionary<string, string> backupOptions = new Dictionary<string, string>(this.TestOptions) { ["symlink-policy"] = symlinkPolicy.ToString() };
+ using (Controller c = new Controller("file://" + this.TARGETFOLDER, backupOptions, null))
+ {
+ IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER });
+ Assert.AreEqual(0, backupResults.Errors.Count());
+ Assert.AreEqual(0, backupResults.Warnings.Count());
+ }
+ // Restore all files
+ using (Controller c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null))
+ {
+ IRestoreResults restoreResults = c.Restore(null);
+ Assert.AreEqual(0, restoreResults.Errors.Count());
+ Assert.AreEqual(0, restoreResults.Warnings.Count());
+
+ // Verify that symlink policy was followed
+ var restoreSymlinkDir = systemIO.PathCombine(this.RESTOREFOLDER, symlinkDirName);
+ switch (symlinkPolicy)
+ {
+ case Options.SymlinkStrategy.Store:
+ // Restore should contain an actual symlink to the original target
+ Assert.That(systemIO.IsSymlink(restoreSymlinkDir), Is.True);
+ var restoredSymlinkFullPath = systemIO.PathGetFullPath(systemIO.GetSymlinkTarget(restoreSymlinkDir));
+ var symlinkTargetFullPath = systemIO.PathGetFullPath(targetDir);
+ Assert.That(restoredSymlinkFullPath, Is.EqualTo(symlinkTargetFullPath));
+ break;
+ case Options.SymlinkStrategy.Follow:
+ // Restore should contain a regular directory with copies of the files in the symlink target
+ Assert.That(systemIO.IsSymlink(restoreSymlinkDir), Is.False);
+ AssertDirectoryTreesAreEquivalent(targetDir, restoreSymlinkDir, $"Symlink policy: {Options.SymlinkStrategy.Store}");
+ break;
+ case Options.SymlinkStrategy.Ignore:
+ // Restore should not contain the symlink at all
+ Assert.That(systemIO.DirectoryExists(restoreSymlinkDir), Is.False);
+ break;
+ }
+ // Prepare for a fresh backup and restore cycle
+ foreach (var dir in systemIO.EnumerateDirectories(this.RESTOREFOLDER))
+ {
+ systemIO.DirectoryDelete(dir, true);
+ }
+ foreach (var file in systemIO.EnumerateFiles(this.RESTOREFOLDER))
+ {
+ systemIO.FileDelete(file);
+ }
+ c.DeleteAllRemoteFiles();
+ if (systemIO.FileExists(this.DBFILE))
+ {
+ systemIO.FileDelete(this.DBFILE);
+ }
+ if (systemIO.FileExists($"{this.DBFILE}-journal"))
+ {
+ systemIO.FileDelete($"{this.DBFILE}-journal");
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Asserts that the two directories are equivalent; i.e., they they contain the same subdirectories and files, recursively.
+ /// </summary>
+ private static void AssertDirectoryTreesAreEquivalent(string expectedDir, string actualDir, string contextMessage)
+ {
+ var localMessage = $"{contextMessage}, in directories {expectedDir} and {actualDir}";
+ var expectedSubdirs = systemIO.EnumerateDirectories(expectedDir).OrderBy(systemIO.PathGetFileName);
+ var actualSubdirs = systemIO.EnumerateDirectories(actualDir).OrderBy(systemIO.PathGetFileName);
+ Assert.That(expectedSubdirs.Select(systemIO.PathGetFileName), Is.EquivalentTo(actualSubdirs.Select(systemIO.PathGetFileName)), localMessage);
+ var expectedSubdirsEnumerator = expectedSubdirs.GetEnumerator();
+ var actualSubdirsEnumerator = actualSubdirs.GetEnumerator();
+ while (expectedSubdirsEnumerator.MoveNext() && actualSubdirsEnumerator.MoveNext())
+ {
+ AssertDirectoryTreesAreEquivalent(expectedSubdirsEnumerator.Current, actualSubdirsEnumerator.Current, contextMessage);
+ }
+ var expectedFiles = systemIO.EnumerateFiles(expectedDir).OrderBy(systemIO.PathGetFileName);
+ var actualFiles = systemIO.EnumerateFiles(actualDir).OrderBy(systemIO.PathGetFileName);
+ Assert.That(expectedFiles.Select(systemIO.PathGetFileName), Is.EquivalentTo(actualFiles.Select(systemIO.PathGetFileName)), localMessage);
+ var expectedFilesEnumerator = expectedFiles.GetEnumerator();
+ var actualFilesEnumerator = actualFiles.GetEnumerator();
+ while (expectedFilesEnumerator.MoveNext() && actualFilesEnumerator.MoveNext())
+ {
+ AssertFilesAreEqual(expectedFilesEnumerator.Current, actualFilesEnumerator.Current, contextMessage);
+ }
+ }
+
+ /// <summary>
+ /// Asserts that two files are the same by comparing their size and their contents.
+ /// </summary>
+ private static void AssertFilesAreEqual(string expectedFile, string actualFile, string contextMessage)
+ {
+ using (var expectedFileStream = systemIO.FileOpenRead(expectedFile))
+ using (var actualFileStream = systemIO.FileOpenRead(actualFile))
+ {
+ Assert.That(expectedFileStream.Length, Is.EqualTo(actualFileStream.Length), $"{contextMessage}, file size mismatch for {expectedFile} and {actualFile}");
+ for (long i = 0; i < expectedFileStream.Length; i++)
+ {
+ var expectedByte = expectedFileStream.ReadByte();
+ var actualByte = actualFileStream.ReadByte();
+ // Only generate message if byte comparison fails
+ if (expectedByte != actualByte)
+ {
+ var message =
+ $"{contextMessage}, file contents mismatch at position {i} for {expectedFile} and {actualFile}";
+ Assert.That(actualByte, Is.EqualTo(expectedByte), message);
+ }
+ }
+ }
+ }
+ }
+}