Files
duplicati/Duplicati/UnitTest/IssueTests.cs
T

280 lines
12 KiB
C#

using Duplicati.Library.DynamicLoader;
using Duplicati.Library.Interface;
using Duplicati.Library.Main;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Threading;
namespace Duplicati.UnitTest
{
public class IssueTests : BasicSetupHelper
{
[Test]
[Category("Targeted")]
public void Issue5023ReferencedFileMissing([Values] bool compactBeforeRecreate)
{
// Reproduction for part of issue #5023
// Error during repair: "Remote file referenced as x by y, but not found in list, registering a missing remote file"
// Can be caused by interrupted index upload followed by compact before the repair
var testopts = new Dictionary<string, string>(TestOptions)
{
["number-of-retries"] = "0",
["dblock-size"] = "20KB",
["threshold"] = "1",
["keep-versions"] = "1",
["no-auto-compact"] = "true",
["no-encryption"] = "true"
};
const long filesize = 1024;
// First backup OK to create valid database
string target = "file://" + TARGETFOLDER;
string file1 = Path.Combine(DATAFOLDER, "f1");
TestUtils.WriteTestFile(file1, filesize);
using (var c = new Library.Main.Controller(target, testopts, null))
{
IBackupResults backupResults = c.Backup(new[] { DATAFOLDER });
TestUtils.AssertResults(backupResults);
}
// Sleep to ensure timestamps are different
Thread.Sleep(1000);
// Fail after index file put
target = new DeterministicErrorBackend().ProtocolKey + "://" + TARGETFOLDER;
string interruptedName = "";
DeterministicErrorBackend.ErrorGenerator = (string action, string remotename) =>
{
// Fail dindex upload
if (action.StartsWith("put") && remotename.Contains("dindex"))
{
interruptedName = remotename;
return true;
}
return false;
};
BackendLoader.AddBackend(new DeterministicErrorBackend());
string file2 = Path.Combine(DATAFOLDER, "f2");
TestUtils.WriteTestFile(file2, filesize);
var uploadEx = Assert.Catch(() =>
{
using (var c = new Library.Main.Controller(target, testopts, null))
{
IBackupResults backupResults = c.Backup(new[] { DATAFOLDER });
}
});
while (uploadEx is AggregateException && uploadEx.InnerException is not null)
uploadEx = uploadEx.InnerException;
Assert.That(uploadEx, Is.TypeOf<DeterministicErrorBackend.DeterministicErrorBackendException>());
Console.WriteLine("Interrupted after upload of {0}", interruptedName);
// Sleep to ensure timestamps are different
Thread.Sleep(1000);
// Complete upload
target = "file://" + TARGETFOLDER;
using (var c = new Library.Main.Controller(target, testopts, null))
{
IBackupResults backupResults = c.Backup(new[] { DATAFOLDER });
TestUtils.AssertResults(backupResults);
}
// At this point there are two index files for the last dblock
Console.WriteLine("Target folder contents (expect extra index file):");
Console.WriteLine(string.Join("\n", from v in Directory.EnumerateFiles(TARGETFOLDER) select Path.GetFileName(v)));
// Note: If there is a recreate here (between the above extra file creation and the following compact),
// the bug does not occur as the extra index file will be recorded in the database and properly deleted by compact
if (compactBeforeRecreate)
{
// Delete f2 and compact
File.Delete(file2);
using (var c = new Library.Main.Controller(target, testopts, null))
{
IBackupResults backupResults = c.Backup(new[] { DATAFOLDER });
TestUtils.AssertResults(backupResults);
ICompactResults compactResults = c.Compact();
TestUtils.AssertResults(compactResults);
Assert.Greater(compactResults.DeletedFileCount, 0);
}
// At this point there are two index files for the last dblock
Console.WriteLine("Target folder contents (expect extra index file):");
Console.WriteLine(string.Join("\n", from v in Directory.EnumerateFiles(TARGETFOLDER) select Path.GetFileName(v)));
}
// Database recreate should work (fails after compact)
File.Delete(DBFILE);
using (var c = new Library.Main.Controller(target, testopts, null))
{
IRepairResults repairResults = c.Repair();
TestUtils.AssertResults(repairResults);
}
}
[Test]
[Category("Disruption"), Category("Bug")]
public void TestSystematicErrors5023()
{
// Attempt to recreate other bugs from #5023, but not successful
var testopts = new Dictionary<string, string>(TestOptions)
{
["number-of-retries"] = "0",
["dblock-size"] = "20KB",
["threshold"] = "1",
["keep-versions"] = "5",
["no-encryption"] = "true",
["disable-synthetic-filelist"] = "true"
};
//testopts["rebuild-missing-dblock-files"] = "true";
string target = "file://" + TARGETFOLDER;
string targetError = new DeterministicErrorBackend().ProtocolKey + "://" + TARGETFOLDER;
int maxFiles = 10;
List<string> files = new List<string>();
bool failed = false;
long accessCounter = 0;
long errorIdx = 0;
DeterministicErrorBackend.ErrorGenerator = (string action, string remotename) =>
{
++accessCounter;
if (accessCounter >= errorIdx)
{
return true;
}
return false;
};
for (int i = 0; i < maxFiles; ++i)
{
string f = Path.Combine(DATAFOLDER, "f" + i);
TestUtils.WriteTestFile(f, 1024 * 20);
files.Add(f);
}
// Initial backup
using (var c = new Library.Main.Controller(target, testopts, null))
{
IBackupResults backupResults = c.Backup(new[] { DATAFOLDER });
TestUtils.AssertResults(backupResults);
}
while (errorIdx < (maxFiles + 2))
{
if (errorIdx % 10 == 0)
{
TestContext.WriteLine("Error index {0}", errorIdx);
}
accessCounter = 0;
try
{
using (var c = new Library.Main.Controller(targetError, testopts, null))
{
IBackupResults backupResults = c.Backup(new[] { DATAFOLDER });
TestUtils.AssertResults(backupResults);
}
}
catch (AssertionException) { throw; }
catch { }
Thread.Sleep(1000);
try
{
using (var c = new Library.Main.Controller(target, testopts, null))
{
IBackupResults backupResults = c.Backup(new[] { DATAFOLDER });
TestUtils.AssertResults(backupResults);
}
}
catch (UserInformationException e)
{
TestContext.WriteLine("Error at index {0}: {1}", errorIdx, e.Message);
if (e.HelpID == "MissingRemoteFiles" || e.HelpID == "ExtraRemoteFiles")
{
using (var c = new Library.Main.Controller(target, testopts, null))
{
IRepairResults repairResults = c.Repair();
TestUtils.AssertResults(repairResults);
}
}
failed = true;
}
Thread.Sleep(1000);
foreach (string f in files)
{
TestUtils.WriteTestFile(f, 1024 * 20);
}
++errorIdx;
}
TestContext.WriteLine("Ran {0} iterations", errorIdx);
Assert.IsFalse(failed);
}
[Test, Sequential]
[Category("Targeted"), Category("Bug"), Category("Non-critical")]
[TestCase(false, true), TestCase(true, true), TestCase(true, false)]
public void Issue5038MissingListBlocklist(bool sameVersion, bool blockFirst)
{
// Backup containing the blocklist of a file BEFORE the file causes a dindex with missing blocklist entry
// This is not critical, because it only requires extra block volume downloads
var testopts = new Dictionary<string, string>(TestOptions)
{
["no-encryption"] = "true"
};
string filename = Path.Combine(DATAFOLDER, "file");
// Start with z to process blockfile after file (at least on some systems)
string blockfile = Path.Combine(DATAFOLDER, blockFirst ? "block" : "zblock");
string target = "file://" + TARGETFOLDER;
byte[] block1 = new byte[10 * 1024];
for (int i = 0; i < block1.Length; ++i)
{
block1[i] = 1;
}
byte[] block2 = new byte[10 * 1024];
for (int i = 0; i < block1.Length; ++i)
{
block1[i] = 2;
}
HashAlgorithm blockhasher = Library.Utility.HashFactory.CreateHasher(new Options(testopts).BlockHashAlgorithm);
var hash1 = blockhasher.ComputeHash(block1, 0, block1.Length);
var hash2 = blockhasher.ComputeHash(block2, 0, block2.Length);
byte[] blockfileContent = hash1.Concat(hash2).ToArray();
TestUtils.WriteFile(blockfile, blockfileContent);
if (!sameVersion)
{
// Backup blockfile first
using (var c = new Library.Main.Controller(target, testopts, null))
{
IBackupResults backupResults = c.Backup(new[] { DATAFOLDER });
TestUtils.AssertResults(backupResults);
}
}
byte[] combined = block1.Concat(block2).ToArray();
TestUtils.WriteFile(filename, combined);
// Backup file that would produce blockfile
using (var c = new Library.Main.Controller(target, testopts, null))
{
IBackupResults backupResults = c.Backup(new[] { DATAFOLDER });
TestUtils.AssertResults(backupResults);
}
// Recreate database downloads block volume
File.Delete(DBFILE);
using (var c = new Library.Main.Controller(target, testopts, null))
{
IRepairResults repairResults = c.Repair();
TestUtils.AssertResults(repairResults);
Assert.IsNull(repairResults.Messages.FirstOrDefault(v => v.Contains("ProcessingRequiredBlocklistVolumes")),
"Blocklist download pass was required");
}
}
}
}