Files
duplicati/Duplicati/UnitTest/RunScriptTests.cs
T
Kenneth SkovhedeandGitHub 713b0b0746 Merge pull request #4417 from warwickmm/handle_unsupported_run_script_exit_codes
Abort with error if run-script-before script returns unsupported code
2021-03-06 14:12:33 +01:00

319 lines
16 KiB
C#

// Copyright (C) 2018, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Duplicati.Library.Common;
using Duplicati.Library.Interface;
using Duplicati.Library.Main;
using NUnit.Framework;
namespace Duplicati.UnitTest
{
public class RunScriptTests : BasicSetupHelper
{
[Test]
[Category("Border")]
public void RunScriptBefore()
{
var blocksize = 10 * 1024;
var options = TestOptions;
options["blocksize"] = blocksize.ToString() + "b";
options["run-script-timeout"] = "5s";
// We need a small delay as we run very small backups back-to-back
var PAUSE_TIME = TimeSpan.FromSeconds(3);
BorderTests.WriteTestFilesToFolder(DATAFOLDER, blocksize, 0);
using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, options, null))
{
var res = c.Backup(new string[] { DATAFOLDER });
Assert.AreEqual(0, res.Errors.Count());
Assert.AreEqual(0, res.Warnings.Count());
if (res.ParsedResult != ParsedResultType.Success)
throw new Exception("Unexpected result from base backup");
System.Threading.Thread.Sleep(PAUSE_TIME);
options["run-script-before"] = CreateScript(0);
res = c.Backup(new string[] { DATAFOLDER });
if (res.ParsedResult != ParsedResultType.Success)
throw new Exception("Unexpected result from backup with return code 0");
if (res.ExaminedFiles <= 0)
throw new Exception("Backup did not examine any files for code 0?");
System.Threading.Thread.Sleep(PAUSE_TIME);
options["run-script-before"] = CreateScript(1);
res = c.Backup(new string[] { DATAFOLDER });
if (res.ParsedResult != ParsedResultType.Success)
throw new Exception("Unexpected result from backup with return code 1");
if (res.ExaminedFiles > 0)
throw new Exception("Backup did examine files for code 1?");
System.Threading.Thread.Sleep(PAUSE_TIME);
options["run-script-before"] = CreateScript(2);
res = c.Backup(new string[] { DATAFOLDER });
if (res.ParsedResult != ParsedResultType.Warning)
throw new Exception("Unexpected result from backup with return code 2");
if (res.ExaminedFiles <= 0)
throw new Exception("Backup did not examine any files for code 2?");
System.Threading.Thread.Sleep(PAUSE_TIME);
options["run-script-before"] = CreateScript(3);
res = c.Backup(new string[] { DATAFOLDER });
if (res.ParsedResult != ParsedResultType.Warning)
throw new Exception("Unexpected result from backup with return code 3");
if (res.ExaminedFiles > 0)
throw new Exception("Backup did examine files for code 3?");
System.Threading.Thread.Sleep(PAUSE_TIME);
options["run-script-before"] = CreateScript(4);
res = c.Backup(new string[] { DATAFOLDER });
if (res.ParsedResult != ParsedResultType.Error)
throw new Exception("Unexpected result from backup with return code 4");
if (res.ExaminedFiles <= 0)
throw new Exception("Backup did not examine any files for code 4?");
foreach (int exitCode in new[] {5, 6, 10, 99})
{
System.Threading.Thread.Sleep(PAUSE_TIME);
options["run-script-before"] = CreateScript(exitCode);
res = c.Backup(new string[] {DATAFOLDER});
if (res.ParsedResult != ParsedResultType.Error)
throw new Exception($"Unexpected result from backup with return code {exitCode}");
if (res.ExaminedFiles > 0)
throw new Exception($"Backup did examine files for code {exitCode}?");
}
System.Threading.Thread.Sleep(PAUSE_TIME);
options["run-script-before"] = CreateScript(2, "TEST WARNING MESSAGE");
res = c.Backup(new string[] { DATAFOLDER });
if (res.ParsedResult != ParsedResultType.Warning)
throw new Exception("Unexpected result from backup with return code 2");
if (res.ExaminedFiles <= 0)
throw new Exception("Backup did examine files for code 2?");
if (!res.Warnings.Any(x => x.IndexOf("TEST WARNING MESSAGE", StringComparison.Ordinal) >= 0))
throw new Exception("Found no warning message in output for code 2");
System.Threading.Thread.Sleep(PAUSE_TIME);
options["run-script-before"] = CreateScript(3, "TEST WARNING MESSAGE");
res = c.Backup(new string[] { DATAFOLDER });
if (res.ParsedResult != ParsedResultType.Warning)
throw new Exception("Unexpected result from backup with return code 3");
if (res.ExaminedFiles > 0)
throw new Exception("Backup did examine files for code 3?");
if (!res.Warnings.Any(x => x.IndexOf("TEST WARNING MESSAGE", StringComparison.Ordinal) >= 0))
throw new Exception("Found no warning message in output for code 3");
System.Threading.Thread.Sleep(PAUSE_TIME);
options["run-script-before"] = CreateScript(4, "TEST ERROR MESSAGE");
res = c.Backup(new string[] { DATAFOLDER });
if (res.ParsedResult != ParsedResultType.Error)
throw new Exception("Unexpected result from backup with return code 4");
if (res.ExaminedFiles <= 0)
throw new Exception("Backup did examine files for code 4?");
if (!res.Errors.Any(x => x.IndexOf("TEST ERROR MESSAGE", StringComparison.Ordinal) >= 0))
throw new Exception("Found no error message in output for code 4");
System.Threading.Thread.Sleep(PAUSE_TIME);
options["run-script-before"] = CreateScript(5, "TEST ERROR MESSAGE");
res = c.Backup(new string[] { DATAFOLDER });
if (res.ParsedResult != ParsedResultType.Error)
throw new Exception("Unexpected result from backup with return code 5");
if (res.ExaminedFiles > 0)
throw new Exception("Backup did examine files for code 5?");
if (!res.Errors.Any(x => x.IndexOf("TEST ERROR MESSAGE", StringComparison.Ordinal) >= 0))
throw new Exception("Found no error message in output for code 5");
System.Threading.Thread.Sleep(PAUSE_TIME);
options["run-script-before"] = CreateScript(0, sleeptime: 10);
res = c.Backup(new string[] { DATAFOLDER });
if (res.ParsedResult != ParsedResultType.Warning)
throw new Exception("Unexpected result from backup with timeout script");
if (res.ExaminedFiles <= 0)
throw new Exception("Backup did not examine any files after timeout?");
}
}
[Test]
[Category("Border")]
[TestCase(0)]
[TestCase(1)]
[TestCase(2)]
[TestCase(3)]
[TestCase(4)]
[TestCase(5)]
[TestCase(6)]
public void RunScriptParsedResult(int exitCode)
{
string parsedResultFile = Path.Combine(this.RESTOREFOLDER, "result.txt");
List<string> customCommands = new List<string>();
if (Platform.IsClientWindows)
{
customCommands.Add($"echo %DUPLICATI__PARSED_RESULT%>\"{parsedResultFile}\"");
}
else
{
customCommands.Add($"echo $DUPLICATI__PARSED_RESULT>\"{parsedResultFile}\"");
}
Dictionary<string, string> options = this.TestOptions;
options["run-script-before"] = this.CreateScript(exitCode);
options["run-script-after"] = this.CreateScript(0, null, null, 0, customCommands);
using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null))
{
IBackupResults backupResults = c.Backup(new[] {this.DATAFOLDER});
bool expectBackup;
string expectedParsedResult;
switch (exitCode)
{
case 0: // OK, run operation
Assert.AreEqual(0, backupResults.Errors.Count());
Assert.AreEqual(0, backupResults.Warnings.Count());
expectBackup = true;
expectedParsedResult = ParsedResultType.Success.ToString();
break;
case 1: // OK, don't run operation
Assert.AreEqual(0, backupResults.Errors.Count());
Assert.AreEqual(0, backupResults.Warnings.Count());
expectBackup = false;
expectedParsedResult = ParsedResultType.Success.ToString();
break;
case 2: // Warning, run operation
Assert.AreEqual(0, backupResults.Errors.Count());
Assert.AreEqual(1, backupResults.Warnings.Count());
expectBackup = true;
expectedParsedResult = ParsedResultType.Warning.ToString();
break;
case 3: // Warning, don't run operation
Assert.AreEqual(0, backupResults.Errors.Count());
Assert.AreEqual(1, backupResults.Warnings.Count());
expectBackup = false;
expectedParsedResult = ParsedResultType.Warning.ToString();
break;
case 4: // Error, run operation
Assert.AreEqual(1, backupResults.Errors.Count());
Assert.AreEqual(0, backupResults.Warnings.Count());
expectBackup = true;
expectedParsedResult = ParsedResultType.Error.ToString();
break;
default: // Error don't run operation
Assert.AreEqual(1, backupResults.Errors.Count());
Assert.AreEqual(0, backupResults.Warnings.Count());
expectBackup = false;
expectedParsedResult = ParsedResultType.Error.ToString();
break;
}
IEnumerable<string> targetEntries = Directory.EnumerateFileSystemEntries(this.TARGETFOLDER);
if (expectBackup)
{
// We expect a dblock, dlist, and dindex file.
Assert.AreEqual(3, targetEntries.Count());
}
else
{
Assert.AreEqual(0, targetEntries.Count());
}
string[] lines = File.ReadAllLines(parsedResultFile);
Assert.AreEqual(1, lines.Length);
Assert.AreEqual(expectedParsedResult, lines[0]);
}
}
[Test]
[Category("Border")]
public void CustomRemoteURL()
{
string customTargetFolder = Path.Combine(this.TARGETFOLDER, "destination");
Directory.CreateDirectory(customTargetFolder);
List<string> customCommands = new List<string>
{
$"echo --remoteurl = \"{customTargetFolder}\""
};
Dictionary<string, string> options = this.TestOptions;
options["run-script-before"] = CreateScript(0, null, null, 0, customCommands);
using (Controller c = new Library.Main.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());
}
string[] targetEntries = Directory.EnumerateFileSystemEntries(this.TARGETFOLDER).ToArray();
Assert.AreEqual(1, targetEntries.Length);
Assert.AreEqual(customTargetFolder, targetEntries[0]);
// We expect a dblock, dlist, and dindex file.
IEnumerable<string> customTargetEntries = Directory.EnumerateFileSystemEntries(customTargetFolder);
Assert.AreEqual(3, customTargetEntries.Count());
}
private string CreateScript(int exitcode, string stderr = null, string stdout = null, int sleeptime = 0, List<string> customCommands = null)
{
var id = Guid.NewGuid().ToString("N").Substring(0, 6);
if (Platform.IsClientWindows)
{
var commands = customCommands ?? new List<string>();
if (!string.IsNullOrWhiteSpace(stdout))
commands.Add($@"echo {stdout}");
if (!string.IsNullOrWhiteSpace(stderr))
commands.Add($@"echo {stderr} 1>&2");
if (sleeptime > 0)
commands.Add($@"sleep {sleeptime}");
commands.Add($"exit {exitcode}");
var filename = Path.GetFullPath(Path.Combine(DATAFOLDER, $"run-script-{id}.bat"));
File.WriteAllLines(filename, commands);
return filename;
}
else
{
var commands = new List<string>();
commands.Add("#!/bin/sh");
if (customCommands != null)
{
commands.AddRange(customCommands);
}
if (!string.IsNullOrWhiteSpace(stdout))
commands.Add($@"echo {stdout}");
if (!string.IsNullOrWhiteSpace(stderr))
commands.Add($@"(>&2 echo {stderr})");
if (sleeptime > 0)
commands.Add($@"sleep {sleeptime}");
commands.Add($"exit {exitcode}");
var filename = Path.GetFullPath(Path.Combine(DATAFOLDER, $"run-script-{id}.sh"));
File.WriteAllLines(filename, commands);
System.Diagnostics.Process.Start("chmod", $@"+x ""{filename}""").WaitForExit();
return filename;
}
}
}
}