This PR changes the defaults when sources are missing. Previously, a missing source would abort the backup, unless `--allow-missing-source` was set. With this change, if no options are set, a missing source will only trigger a warning. If the option `--allow-missing-source` is set, no warning is logged. To get the previous behavior, a new option `--abort-if-source-missing` is introduced that toggles this. If no sources are found at all, the backup will abort no matter the flags.
105 lines
4.7 KiB
C#
105 lines
4.7 KiB
C#
// Copyright (C) 2026, 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 System.IO;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using Duplicati.Library.Interface;
|
|
using NUnit.Framework;
|
|
|
|
namespace Duplicati.UnitTest
|
|
{
|
|
public class MissingSourceTest : BasicSetupHelper
|
|
{
|
|
/// <summary>
|
|
/// A source path that is guaranteed not to exist
|
|
/// </summary>
|
|
private string MissingFolder => Path.Combine(BASEFOLDER, "this-source-does-not-exist") + Path.DirectorySeparatorChar;
|
|
|
|
/// <summary>
|
|
/// Writes a file into the (existing, but empty) DATAFOLDER so it is a valid, non-empty source
|
|
/// </summary>
|
|
private void PopulateDataFolder()
|
|
{
|
|
TestUtils.WriteTestFile(Path.Combine(DATAFOLDER, "file.txt"), 1024);
|
|
}
|
|
|
|
[Test]
|
|
[Category("Targeted")]
|
|
public async Task BackupContinuesWhenSourceIsMissingByDefaultAsync()
|
|
{
|
|
// With the default options, a missing source should only emit a warning and continue,
|
|
// as long as at least one valid source remains.
|
|
PopulateDataFolder();
|
|
var testopts = TestOptions.Expand(new { });
|
|
|
|
using var controller = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null);
|
|
var result = await controller.BackupAsync(new[] { DATAFOLDER, MissingFolder });
|
|
|
|
Assert.That(result, Is.Not.Null);
|
|
Assert.That(result.Warnings.Any(w => w.Contains("does not exist")), Is.True,
|
|
"A warning should be emitted for the missing source");
|
|
}
|
|
|
|
[Test]
|
|
[Category("Targeted")]
|
|
public void BackupAbortsWhenSourceIsMissingAndAbortOptionSet()
|
|
{
|
|
// When abort-if-source-missing is set, a missing source should abort the backup.
|
|
PopulateDataFolder();
|
|
var testopts = TestOptions.Expand(new { abort_if_source_missing = true });
|
|
|
|
using var controller = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null);
|
|
var ex = Assert.ThrowsAsync<UserInformationException>(() => controller.BackupAsync(new[] { DATAFOLDER, MissingFolder }));
|
|
Assert.That(ex!.HelpID, Is.EqualTo("SourceIsMissing"));
|
|
}
|
|
|
|
[Test]
|
|
[Category("Targeted")]
|
|
public async Task BackupDoesNotWarnWhenSourceIsMissingAndAllowOptionSetAsync()
|
|
{
|
|
// When allow-missing-source is set, a missing source should be silently skipped.
|
|
PopulateDataFolder();
|
|
var testopts = TestOptions.Expand(new { allow_missing_source = true });
|
|
|
|
using var controller = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null);
|
|
var result = await controller.BackupAsync(new[] { DATAFOLDER, MissingFolder });
|
|
|
|
Assert.That(result, Is.Not.Null);
|
|
Assert.That(result.Warnings.Any(w => w.Contains("does not exist")), Is.False,
|
|
"No missing-source warning should be emitted when allow-missing-source is set");
|
|
}
|
|
|
|
[Test]
|
|
[Category("Targeted")]
|
|
public void BackupAbortsWhenAllSourcesMissing()
|
|
{
|
|
// If every source is missing, the backup would be empty, so it should abort even
|
|
// with the default (non-abort) options.
|
|
var testopts = TestOptions.Expand(new { });
|
|
|
|
using var controller = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null);
|
|
var ex = Assert.ThrowsAsync<UserInformationException>(() => controller.BackupAsync(new[] { MissingFolder }));
|
|
Assert.That(ex!.HelpID, Is.EqualTo("NoSources"));
|
|
}
|
|
}
|
|
}
|