184 lines
6.8 KiB
C#
184 lines
6.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Duplicati.Library.Common.IO;
|
|
using NUnit.Framework;
|
|
using Assert = NUnit.Framework.Legacy.ClassicAssert;
|
|
|
|
namespace Duplicati.UnitTest
|
|
{
|
|
public class Issue6366 : BasicSetupHelper
|
|
{
|
|
// Disabled because it takes too long to run, and the value is fairly low
|
|
// [Test]
|
|
// [Category("Purge")]
|
|
// [Category("Performance")]
|
|
public async Task TestPurgeBrokenFilesPerformance()
|
|
{
|
|
var blocksize = 1024 * 10;
|
|
var numberOfVersions = 50; // Simulate many versions as in the issue
|
|
var filesPerVersion = 200; // Many files per version
|
|
|
|
var testopts = TestOptions;
|
|
testopts["blocksize"] = blocksize.ToString() + "b";
|
|
testopts["no-backend-verification"] = "true";
|
|
|
|
// Create initial set of files
|
|
var allFiles = new List<string>();
|
|
for (int i = 0; i < filesPerVersion; i++)
|
|
{
|
|
var filename = Path.Combine(DATAFOLDER, $"testfile_{i}.dat");
|
|
File.WriteAllBytes(filename, new byte[blocksize * 2]);
|
|
allFiles.Add(filename);
|
|
}
|
|
|
|
// Create multiple backup versions
|
|
string firstDblockFile = null;
|
|
for (int version = 0; version < numberOfVersions; version++)
|
|
{
|
|
// Modify some files to create new versions
|
|
foreach (var file in allFiles.Take(10))
|
|
{
|
|
File.WriteAllBytes(file, new byte[blocksize * 2 + version]);
|
|
}
|
|
|
|
using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null))
|
|
{
|
|
var res = c.Backup(new string[] { DATAFOLDER });
|
|
Assert.AreEqual(0, res.Errors.Count());
|
|
}
|
|
|
|
// Capture the first dblock file
|
|
if (version == 0)
|
|
{
|
|
firstDblockFile = SystemIO.IO_OS
|
|
.GetFiles(TARGETFOLDER, "*.dblock.zip.aes")
|
|
.First();
|
|
}
|
|
|
|
// Ensure timestamps are different
|
|
Thread.Sleep(1000);
|
|
}
|
|
|
|
// Delete a dblock file to create broken files
|
|
if (firstDblockFile != null)
|
|
{
|
|
File.Delete(firstDblockFile);
|
|
}
|
|
else
|
|
{
|
|
Assert.Fail("Could not find dblock file to delete");
|
|
}
|
|
|
|
// Measure purge-broken-files execution time
|
|
var stopwatch = Stopwatch.StartNew();
|
|
|
|
using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null))
|
|
{
|
|
var task = Task.Run(() => c.PurgeBrokenFiles(null));
|
|
if (await Task.WhenAny(task, Task.Delay(60000)) == task)
|
|
{
|
|
var brk = await task;
|
|
Assert.AreEqual(0, brk.Errors.Count());
|
|
Assert.AreEqual(0, brk.Warnings.Count());
|
|
}
|
|
else
|
|
{
|
|
c.Abort();
|
|
Assert.Fail("PurgeBrokenFiles timed out");
|
|
}
|
|
}
|
|
|
|
stopwatch.Stop();
|
|
Console.WriteLine($"PurgeBrokenFiles took: {stopwatch.Elapsed.TotalSeconds} seconds");
|
|
|
|
// The operation should complete in a reasonable time
|
|
// With the fix, this should be under 60 seconds
|
|
// Without the fix, this could take hours
|
|
Assert.Less(stopwatch.Elapsed.TotalSeconds, 60,
|
|
$"PurgeBrokenFiles took too long: {stopwatch.Elapsed}");
|
|
}
|
|
|
|
[Test]
|
|
[Category("Purge")]
|
|
public void TestPurgeBrokenFilesReducedStatistics()
|
|
{
|
|
var blocksize = 1024 * 10;
|
|
var numberOfVersions = 3;
|
|
var filesPerVersion = 10;
|
|
|
|
var testopts = TestOptions;
|
|
testopts["blocksize"] = blocksize.ToString() + "b";
|
|
testopts["reduced-purge-statistics"] = "true";
|
|
|
|
// Create initial set of files
|
|
var allFiles = new List<string>();
|
|
var rnd = new Random(42);
|
|
for (int i = 0; i < filesPerVersion; i++)
|
|
{
|
|
var filename = Path.Combine(DATAFOLDER, $"testfile_{i}.dat");
|
|
var bytes = new byte[blocksize * 2];
|
|
rnd.NextBytes(bytes);
|
|
File.WriteAllBytes(filename, bytes);
|
|
allFiles.Add(filename);
|
|
}
|
|
|
|
// Create multiple backup versions
|
|
string dblockToDelete = null;
|
|
for (int version = 0; version < numberOfVersions; version++)
|
|
{
|
|
// Modify ALL files to create new versions with unique content
|
|
foreach (var file in allFiles)
|
|
{
|
|
var bytes = new byte[blocksize * 2 + version * 100];
|
|
rnd.NextBytes(bytes);
|
|
File.WriteAllBytes(file, bytes);
|
|
}
|
|
|
|
using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null))
|
|
{
|
|
var res = c.Backup(new string[] { DATAFOLDER });
|
|
Assert.AreEqual(0, res.Errors.Count());
|
|
}
|
|
|
|
// Capture the dblock file from the second version (index 1)
|
|
if (version == 1)
|
|
{
|
|
// Find the new dblock file.
|
|
// We need to distinguish it from previous ones.
|
|
// Since we sleep, timestamps might help, or we can just list all and pick one we haven't seen?
|
|
// Simpler: just pick the one created most recently.
|
|
var dblocks = SystemIO.IO_OS.GetFiles(TARGETFOLDER, "*.dblock.zip.aes");
|
|
dblockToDelete = dblocks.OrderByDescending(f => new FileInfo(f).CreationTime).First();
|
|
}
|
|
|
|
Thread.Sleep(2000); // Ensure filesystem timestamp granularity
|
|
}
|
|
|
|
// Delete the dblock file
|
|
if (dblockToDelete != null)
|
|
{
|
|
File.Delete(dblockToDelete);
|
|
}
|
|
else
|
|
{
|
|
Assert.Fail("Could not find dblock file to delete");
|
|
}
|
|
|
|
using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null))
|
|
{
|
|
var brk = c.PurgeBrokenFiles(null);
|
|
Assert.AreEqual(0, brk.Errors.Count());
|
|
Assert.AreEqual(0, brk.Warnings.Count());
|
|
|
|
Assert.IsNotNull(brk.PurgeResults);
|
|
Assert.AreEqual(-1, brk.PurgeResults.RemovedFileSize);
|
|
}
|
|
}
|
|
}
|
|
}
|