示例#1
0
        public void ProblematicSuffixes(string pathComponent, bool skipOnWindows)
        {
            if (Platform.IsClientWindows && skipOnWindows)
            {
                return;
            }

            string folderPath = SystemIO.IO_OS.PathCombine(this.DATAFOLDER, pathComponent);

            SystemIO.IO_OS.DirectoryCreate(folderPath);

            string filePath = SystemIO.IO_OS.PathCombine(folderPath, pathComponent);

            byte[] fileBytes = { 0, 1, 2 };
            TestUtils.WriteFile(filePath, fileBytes);

            Dictionary <string, string> options = new Dictionary <string, string>(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());
            }

            Dictionary <string, string> restoreOptions = new Dictionary <string, string>(this.TestOptions)
            {
                ["restore-path"] = this.RESTOREFOLDER
            };

            // Restore just the file.
            using (Controller 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());
            }

            string restoreFilePath = SystemIO.IO_OS.PathCombine(this.RESTOREFOLDER, pathComponent);

            TestUtils.AssertFilesAreEqual(filePath, restoreFilePath, true, pathComponent);
            SystemIO.IO_OS.FileDelete(restoreFilePath);

            // Restore the entire directory.
            string pathSpec = $"[{Regex.Escape(Util.AppendDirSeparator(this.DATAFOLDER))}.*]";

            using (Controller c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null))
            {
                IRestoreResults restoreResults = c.Restore(new[] { pathSpec });
                Assert.AreEqual(0, restoreResults.Errors.Count());
                Assert.AreEqual(0, restoreResults.Warnings.Count());
            }

            TestUtils.AssertDirectoryTreesAreEquivalent(this.DATAFOLDER, this.RESTOREFOLDER, true, pathComponent);
        }
        public void RestoreEmptyFile()
        {
            string folderPath = Path.Combine(this.DATAFOLDER, "folder");

            Directory.CreateDirectory(folderPath);
            string filePath = Path.Combine(folderPath, "empty_file");

            File.WriteAllBytes(filePath, new byte[] { });

            Dictionary <string, string> options = new Dictionary <string, string>(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());
            }

            // Issue #4148 described a situation where the folders containing the empty file were not recreated properly.
            Dictionary <string, string> restoreOptions = new Dictionary <string, string>(this.TestOptions)
            {
                ["restore-path"] = this.RESTOREFOLDER,
                ["dont-compress-restore-paths"] = "true"
            };

            using (Controller 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());
            }

            // We need to strip the root part of the path.  Otherwise, Path.Combine will simply return the second argument
            // if it's determined to be an absolute path.
            string rootString  = SystemIO.IO_OS.GetPathRoot(filePath);
            string newPathPart = filePath.Substring(rootString.Length);

            if (Platform.IsClientWindows)
            {
                // On Windows, the drive letter is included in the path when the dont-compress-restore-paths option is used.
                // The drive letter is assumed to be the first character of the path root (e.g., C:\).
                newPathPart = Path.Combine(rootString.Substring(0, 1), filePath.Substring(rootString.Length));
            }

            string restoredFilePath = Path.Combine(restoreOptions["restore-path"], newPathPart);

            Assert.IsTrue(File.Exists(restoredFilePath));
        }
        public void LongPath()
        {
            string folderPath = Path.Combine(this.DATAFOLDER, new string('x', 10));

            SystemIO.IO_OS.DirectoryCreate(folderPath);

            string fileName = new string('y', 255);
            string filePath = SystemIO.IO_OS.PathCombine(folderPath, fileName);

            byte[] fileBytes = { 0, 1, 2 };
            WriteFile(filePath, fileBytes);

            Dictionary <string, string> options = new Dictionary <string, string>(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());
            }

            Dictionary <string, string> restoreOptions = new Dictionary <string, string>(this.TestOptions)
            {
                ["restore-path"] = this.RESTOREFOLDER
            };

            using (Controller 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());
            }

            string restoreFilePath = SystemIO.IO_OS.PathCombine(this.RESTOREFOLDER, fileName);

            Assert.IsTrue(SystemIO.IO_OS.FileExists(restoreFilePath));

            MemoryStream restoredStream = new MemoryStream();

            using (FileStream fileStream = SystemIO.IO_OS.FileOpenRead(restoreFilePath))
            {
                Utility.CopyStream(fileStream, restoredStream);
            }

            Assert.AreEqual(fileBytes, restoredStream.ToArray());
        }
示例#4
0
        public void DirectoriesWithWildcards()
        {
            const string  file        = "file";
            List <string> directories = new List <string>();

            // Keep expected match counts since they'll differ between
            // Linux and Windows.
            var questionMarkWildcardShouldMatchCount = 0;
            var verbatimAsteriskShouldMatchCount     = 0;

            const string asterisk        = "*";
            string       dirWithAsterisk = Path.Combine(this.DATAFOLDER, asterisk);

            // Windows does not support literal asterisks in paths.
            if (!Platform.IsClientWindows)
            {
                SystemIO.IO_OS.DirectoryCreate(dirWithAsterisk);
                WriteFile(SystemIO.IO_OS.PathCombine(dirWithAsterisk, file), new byte[] { 0 });
                directories.Add(dirWithAsterisk);
                questionMarkWildcardShouldMatchCount++;
                verbatimAsteriskShouldMatchCount++;
            }

            const string questionMark        = "?";
            string       dirWithQuestionMark = Path.Combine(this.DATAFOLDER, questionMark);

            // Windows does not support literal question marks in paths.
            if (!Platform.IsClientWindows)
            {
                SystemIO.IO_OS.DirectoryCreate(dirWithQuestionMark);
                WriteFile(SystemIO.IO_OS.PathCombine(dirWithQuestionMark, file), new byte[] { 1 });
                directories.Add(dirWithQuestionMark);
                questionMarkWildcardShouldMatchCount++;
            }

            // Include at least one single character directory in Windows
            // for a '?' wildcard can match on
            const string singleCharacterDir     = "X";
            string       dirWithSingleCharacter = Path.Combine(this.DATAFOLDER, singleCharacterDir);

            SystemIO.IO_OS.DirectoryCreate(dirWithSingleCharacter);
            WriteFile(SystemIO.IO_OS.PathCombine(dirWithSingleCharacter, file), new byte[] { 2 });
            directories.Add(dirWithSingleCharacter);
            questionMarkWildcardShouldMatchCount++;

            const string dir       = "dir";
            string       normalDir = Path.Combine(this.DATAFOLDER, dir);

            SystemIO.IO_OS.DirectoryCreate(normalDir);
            WriteFile(SystemIO.IO_OS.PathCombine(normalDir, file), new byte[] { 3 });
            directories.Add(normalDir);

            // Backup all files.
            Dictionary <string, string> options = new Dictionary <string, string>(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());
            }

            // Restore all files.
            Dictionary <string, string> restoreOptions = new Dictionary <string, string>(options)
            {
                ["restore-path"] = this.RESTOREFOLDER
            };

            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());

                foreach (string directory in directories)
                {
                    string directoryName = SystemIO.IO_OS.PathGetFileName(directory);
                    foreach (string expectedFilePath in SystemIO.IO_OS.EnumerateFiles(directory))
                    {
                        string fileName         = SystemIO.IO_OS.PathGetFileName(expectedFilePath);
                        string restoredFilePath = SystemIO.IO_OS.PathCombine(this.RESTOREFOLDER, directoryName, fileName);
                        Assert.IsTrue(TestUtils.CompareFiles(expectedFilePath, restoredFilePath, expectedFilePath, false));
                    }
                }

                // List results using * should return a match for each directory.
                IListResults listResults = c.List(SystemIO.IO_OS.PathCombine(dirWithAsterisk, file));
                Assert.AreEqual(0, listResults.Errors.Count());
                Assert.AreEqual(0, listResults.Warnings.Count());
                Assert.AreEqual(directories.Count, listResults.Files.Count());

                listResults = c.List(SystemIO.IO_OS.PathCombine(dirWithQuestionMark, file));
                Assert.AreEqual(0, listResults.Errors.Count());
                Assert.AreEqual(0, listResults.Warnings.Count());
                // List results using ? should return 3 matches in Linux,
                // one for the directory with '*' and one for the directory
                // with '?', plus one for directory 'X'; but should return
                // 1 matches in Windows just for directory 'X'.
                Assert.AreEqual(questionMarkWildcardShouldMatchCount, listResults.Files.Count());
            }

            SystemIO.IO_OS.DirectoryDelete(this.RESTOREFOLDER, true);

            // Restore one file at a time using the verbatim identifier.
            foreach (string directory in directories)
            {
                foreach (string expectedFilePath in SystemIO.IO_OS.EnumerateFiles(directory))
                {
                    using (Controller c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null))
                    {
                        string verbatimFilePath = "@" + expectedFilePath;

                        // Verify that list result using verbatim identifier contains only one file.
                        IListResults listResults = c.List(verbatimFilePath);
                        Assert.AreEqual(0, listResults.Errors.Count());
                        Assert.AreEqual(0, listResults.Warnings.Count());
                        Assert.AreEqual(1, listResults.Files.Count());
                        Assert.AreEqual(expectedFilePath, listResults.Files.Single().Path);

                        IRestoreResults restoreResults = c.Restore(new[] { verbatimFilePath });
                        Assert.AreEqual(0, restoreResults.Errors.Count());
                        Assert.AreEqual(0, restoreResults.Warnings.Count());

                        string fileName         = SystemIO.IO_OS.PathGetFileName(expectedFilePath);
                        string restoredFilePath = SystemIO.IO_OS.PathCombine(this.RESTOREFOLDER, fileName);
                        Assert.IsTrue(TestUtils.CompareFiles(expectedFilePath, restoredFilePath, expectedFilePath, false));

                        SystemIO.IO_OS.FileDelete(restoredFilePath);
                    }
                }
            }

            // Backup with asterisk in include filter should include all directories.
            FilterExpression filter = new FilterExpression(dirWithAsterisk);

            using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null))
            {
                IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }, filter);
                Assert.AreEqual(0, backupResults.Errors.Count());
                Assert.AreEqual(0, backupResults.Warnings.Count());
                Assert.AreEqual(directories.Count, backupResults.ExaminedFiles);
            }

            // Backup with verbatim asterisk in include filter should include
            // one directory in Linux and zero directories in Windows.
            filter = new FilterExpression("@" + SystemIO.IO_OS.PathCombine(dirWithAsterisk, file));
            using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null))
            {
                IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }, filter);
                Assert.AreEqual(0, backupResults.Errors.Count());
                Assert.AreEqual(0, backupResults.Warnings.Count());
                Assert.AreEqual(verbatimAsteriskShouldMatchCount, backupResults.ExaminedFiles);
            }
        }
示例#5
0
        public async Task StopNow()
        {
            // Choose a dblock size that is small enough so that more than one volume is needed.
            Dictionary <string, string> options = new Dictionary <string, string>(this.TestOptions)
            {
                ["dblock-size"] = "10mb", ["disable-synthetic-filelist"] = "true"
            };

            // Run a complete backup.
            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());

                List <IListResultFileset> filesets = c.List().Filesets.ToList();
                Assert.AreEqual(1, filesets.Count);
                Assert.AreEqual(BackupType.FULL_BACKUP, filesets[0].IsFullBackup);
            }

            // Interrupt a backup with "stop now".
            this.ModifySourceFiles();
            using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null))
            {
                // ReSharper disable once AccessToDisposedClosure
                Task backupTask = Task.Run(() => c.Backup(new[] { this.DATAFOLDER }));

                // Block for a small amount of time to allow the ITaskControl to be associated
                // with the Controller.  Otherwise, the call to Stop will simply be a no-op.
                Thread.Sleep(1000);

                c.Stop(false);
                await backupTask.ConfigureAwait(false);
            }

            // The next backup should proceed without issues.
            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());

                List <IListResultFileset> filesets = c.List().Filesets.ToList();
                Assert.AreEqual(2, filesets.Count);
                Assert.AreEqual(BackupType.FULL_BACKUP, filesets[1].IsFullBackup);
                Assert.AreEqual(BackupType.FULL_BACKUP, filesets[0].IsFullBackup);
            }

            // Restore from the backup that followed the interruption.
            Dictionary <string, string> restoreOptions = new Dictionary <string, string>(options)
            {
                ["restore-path"] = this.RESTOREFOLDER
            };

            using (Controller c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null))
            {
                IListResults lastResults      = c.List("*");
                string[]     fullVersionFiles = lastResults.Files.Select(x => x.Path).Where(x => !Utility.IsFolder(x, File.GetAttributes)).ToArray();
                Assert.AreEqual(this.fileSizes.Length, fullVersionFiles.Length);

                IRestoreResults restoreResults = c.Restore(fullVersionFiles);
                Assert.AreEqual(0, restoreResults.Errors.Count());
                Assert.AreEqual(0, restoreResults.Warnings.Count());

                foreach (string filepath in fullVersionFiles)
                {
                    string filename = Path.GetFileName(filepath);
                    TestUtils.AssertFilesAreEqual(filepath, Path.Combine(this.RESTOREFOLDER, filename ?? String.Empty), false, filename);
                }
            }
        }
示例#6
0
        public async Task StopAfterCurrentFile()
        {
            // Choose a dblock size that is small enough so that more than one volume is needed.
            Dictionary <string, string> options = new Dictionary <string, string>(this.TestOptions)
            {
                ["dblock-size"] = "10mb"
            };

            // Run a complete backup.
            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());

                Assert.AreEqual(1, c.List().Filesets.Count());
                Assert.AreEqual(BackupType.FULL_BACKUP, c.List().Filesets.Single(x => x.Version == 0).IsFullBackup);
            }

            // Run a partial backup.
            using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null))
            {
                IBackupResults backupResults = await this.RunPartialBackup(c).ConfigureAwait(false);

                Assert.AreEqual(0, backupResults.Errors.Count());
                Assert.AreEqual(1, backupResults.Warnings.Count());

                // If we interrupt the backup, the most recent Fileset should be marked as partial.
                Assert.AreEqual(2, c.List().Filesets.Count());
                Assert.AreEqual(BackupType.FULL_BACKUP, c.List().Filesets.Single(x => x.Version == 1).IsFullBackup);
                Assert.AreEqual(BackupType.PARTIAL_BACKUP, c.List().Filesets.Single(x => x.Version == 0).IsFullBackup);
            }

            // Restore files from the partial backup set.
            Dictionary <string, string> restoreOptions = new Dictionary <string, string>(options)
            {
                ["restore-path"] = this.RESTOREFOLDER
            };

            using (Controller c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null))
            {
                IListResults lastResults         = c.List("*");
                string[]     partialVersionFiles = lastResults.Files.Select(x => x.Path).Where(x => !Utility.IsFolder(x, File.GetAttributes)).ToArray();
                Assert.GreaterOrEqual(partialVersionFiles.Length, 1);
                c.Restore(partialVersionFiles);

                foreach (string filepath in partialVersionFiles)
                {
                    string filename = Path.GetFileName(filepath);
                    TestUtils.AssertFilesAreEqual(filepath, Path.Combine(this.RESTOREFOLDER, filename ?? String.Empty), false, filename);
                }
            }

            // Recreating the database should preserve the backup types.
            File.Delete(this.DBFILE);
            using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null))
            {
                IRepairResults repairResults = c.Repair();
                Assert.AreEqual(0, repairResults.Errors.Count());
                Assert.AreEqual(0, repairResults.Warnings.Count());

                Assert.AreEqual(2, c.List().Filesets.Count());
                Assert.AreEqual(BackupType.FULL_BACKUP, c.List().Filesets.Single(x => x.Version == 1).IsFullBackup);
                Assert.AreEqual(BackupType.PARTIAL_BACKUP, c.List().Filesets.Single(x => x.Version == 0).IsFullBackup);
            }

            // Run a complete backup.  Listing the Filesets should include both full and partial backups.
            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());
                Assert.AreEqual(3, c.List().Filesets.Count());

                Assert.AreEqual(BackupType.FULL_BACKUP, c.List().Filesets.Single(x => x.Version == 2).IsFullBackup);
                Assert.AreEqual(BackupType.PARTIAL_BACKUP, c.List().Filesets.Single(x => x.Version == 1).IsFullBackup);
                Assert.AreEqual(BackupType.FULL_BACKUP, c.List().Filesets.Single(x => x.Version == 0).IsFullBackup);
            }

            // Restore files from the full backup set.
            restoreOptions["overwrite"] = "true";
            using (Controller c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null))
            {
                IListResults lastResults      = c.List("*");
                string[]     fullVersionFiles = lastResults.Files.Select(x => x.Path).Where(x => !Utility.IsFolder(x, File.GetAttributes)).ToArray();
                Assert.AreEqual(this.fileSizes.Length, fullVersionFiles.Length);

                IRestoreResults restoreResults = c.Restore(fullVersionFiles);
                Assert.AreEqual(0, restoreResults.Errors.Count());
                Assert.AreEqual(0, restoreResults.Warnings.Count());

                foreach (string filepath in fullVersionFiles)
                {
                    string filename = Path.GetFileName(filepath);
                    TestUtils.AssertFilesAreEqual(filepath, Path.Combine(this.RESTOREFOLDER, filename ?? String.Empty), false, filename);
                }
            }
        }
示例#7
0
        public void RestoreInheritanceBreaks()
        {
            if (!Platform.IsClientWindows)
            {
                return;
            }

            string folderPath = Path.Combine(this.DATAFOLDER, "folder");

            Directory.CreateDirectory(folderPath);
            string filePath = Path.Combine(folderPath, "file");

            File.WriteAllBytes(filePath, new byte[] { 0 });

            // Protect access rules on the file.
            FileSecurity fileSecurity = File.GetAccessControl(filePath);

            fileSecurity.SetAccessRuleProtection(true, true);
            File.SetAccessControl(filePath, fileSecurity);

            Dictionary <string, string> options = new Dictionary <string, string>(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());
            }

            // First, restore without restoring permissions.
            Dictionary <string, string> restoreOptions = new Dictionary <string, string>(this.TestOptions)
            {
                ["restore-path"] = this.RESTOREFOLDER
            };

            using (Controller 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());

                string restoredFilePath = Path.Combine(this.RESTOREFOLDER, "file");
                Assert.IsTrue(File.Exists(restoredFilePath));

                FileSecurity restoredFileSecurity = File.GetAccessControl(restoredFilePath);
                Assert.IsFalse(restoredFileSecurity.AreAccessRulesProtected);

                // Remove the restored file so that the later restore avoids the "Restore completed
                // without errors but no files were restored" warning.
                File.Delete(restoredFilePath);
            }

            // Restore with restoring permissions.
            restoreOptions["overwrite"]           = "true";
            restoreOptions["restore-permissions"] = "true";
            using (Controller 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());

                string restoredFilePath = Path.Combine(this.RESTOREFOLDER, "file");
                Assert.IsTrue(File.Exists(restoredFilePath));

                FileSecurity restoredFileSecurity = File.GetAccessControl(restoredFilePath);
                Assert.IsTrue(restoredFileSecurity.AreAccessRulesProtected);
            }
        }
示例#8
0
        public void SymLinkPolicy(Options.SymlinkStrategy 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);
                TestUtils.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}");
            }

            // Backup all files with given symlink policy
            Dictionary <string, string> restoreOptions = new Dictionary <string, string>(this.TestOptions)
            {
                ["restore-path"] = this.RESTOREFOLDER
            };
            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);
                    TestUtils.AssertDirectoryTreesAreEquivalent(targetDir, restoreSymlinkDir, true, "Restore");
                    break;

                case Options.SymlinkStrategy.Ignore:
                    // Restore should not contain the symlink or directory at all
                    Assert.That(systemIO.DirectoryExists(restoreSymlinkDir), Is.False);
                    Assert.That(systemIO.FileExists(restoreSymlinkDir), Is.False);
                    break;

                default:
                    Assert.Fail($"Unexpected symlink policy");
                    break;
                }
            }
        }