Merge branch 'feature/improved-exit-code-handling'
This commit is contained in:
@@ -139,5 +139,48 @@ namespace Duplicati.Library.Interface
|
||||
public CancelException(string message, Exception innerException)
|
||||
: base(message, "Cancelled", innerException)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The reason why an operation is aborted
|
||||
/// </summary>
|
||||
public enum OperationAbortReason
|
||||
{
|
||||
/// <summary>
|
||||
/// The operation is aborted, but this is considered a normal operation
|
||||
/// </summary>
|
||||
Normal,
|
||||
/// <summary>
|
||||
/// The operation is aborted and this should give a warning
|
||||
/// </summary>
|
||||
Warning,
|
||||
/// <summary>
|
||||
/// The operation is aborted and this is an error
|
||||
/// </summary>
|
||||
Error
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A class that signals the operation should be aborted
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class OperationAbortException : UserInformationException
|
||||
{
|
||||
/// <summary>
|
||||
/// The reason for the abort operation
|
||||
/// </summary>
|
||||
public readonly OperationAbortReason AbortReason;
|
||||
|
||||
public OperationAbortException(OperationAbortReason reason, string message)
|
||||
: base(message, "OperationAborted")
|
||||
{
|
||||
AbortReason = reason;
|
||||
}
|
||||
|
||||
public OperationAbortException(OperationAbortReason reason, string message, Exception innerException)
|
||||
: base(message, "OperationAborted", innerException)
|
||||
{
|
||||
AbortReason = reason;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -430,14 +430,29 @@ namespace Duplicati.Library.Main
|
||||
{
|
||||
result.EndTime = DateTime.UtcNow;
|
||||
|
||||
try { (result as BasicResults).OperationProgressUpdater.UpdatePhase(OperationPhase.Error); }
|
||||
catch { }
|
||||
if (ex is Library.Interface.OperationAbortException oae)
|
||||
{
|
||||
// Perform the module shutdown
|
||||
OnOperationComplete(ex);
|
||||
|
||||
OnOperationComplete(ex);
|
||||
// Log this as a normal operation, as the script rasing the exception,
|
||||
// has already populated either warning or log messages as required
|
||||
Logging.Log.WriteInformationMessage(LOGTAG, "AbortOperation", "Aborting operation by request, requested result: {0}", oae.AbortReason);
|
||||
|
||||
Logging.Log.WriteErrorMessage(LOGTAG, "FailedOperation", ex, Strings.Controller.FailedOperationMessage(m_options.MainAction, ex.Message));
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
try { (result as BasicResults).OperationProgressUpdater.UpdatePhase(OperationPhase.Error); }
|
||||
catch { }
|
||||
|
||||
OnOperationComplete(ex);
|
||||
|
||||
Logging.Log.WriteErrorMessage(LOGTAG, "FailedOperation", ex, Strings.Controller.FailedOperationMessage(m_options.MainAction, ex.Message));
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -24,6 +24,7 @@ using Duplicati.Library.Utility;
|
||||
using Duplicati.Library.Interface;
|
||||
using System.Linq;
|
||||
using Duplicati.Library.Modules.Builtin.ResultSerialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Duplicati.Library.Modules.Builtin
|
||||
{
|
||||
@@ -220,7 +221,7 @@ namespace Duplicati.Library.Modules.Builtin
|
||||
psi.EnvironmentVariables["DUPLICATI__REMOTEURL"] = remoteurl;
|
||||
if (level != null)
|
||||
psi.EnvironmentVariables["DUPLICATI__PARSED_RESULT"] = level.Value.ToString();
|
||||
|
||||
|
||||
if (localpath != null)
|
||||
psi.EnvironmentVariables["DUPLICATI__LOCALPATH"] = string.Join(System.IO.Path.PathSeparator.ToString(), localpath);
|
||||
|
||||
@@ -230,9 +231,9 @@ namespace Duplicati.Library.Modules.Builtin
|
||||
if (!string.IsNullOrEmpty(datafile))
|
||||
psi.EnvironmentVariables["DUPLICATI__RESULTFILE"] = datafile;
|
||||
|
||||
using(System.Diagnostics.Process p = System.Diagnostics.Process.Start(psi))
|
||||
using (System.Diagnostics.Process p = System.Diagnostics.Process.Start(psi))
|
||||
{
|
||||
ConsoleDataHandler cs = new ConsoleDataHandler(p);
|
||||
var cs = new ConsoleDataHandler(p);
|
||||
|
||||
if (timeout <= 0)
|
||||
p.WaitForExit();
|
||||
@@ -249,14 +250,57 @@ namespace Duplicati.Library.Modules.Builtin
|
||||
|
||||
if (p.HasExited)
|
||||
{
|
||||
cs.WaitForCompletion();
|
||||
|
||||
stderr = cs.StandardError;
|
||||
stdout = cs.StandardOutput;
|
||||
if (p.ExitCode != 0)
|
||||
Logging.Log.WriteWarningMessage(LOGTAG, "InvalidExitCode", null, Strings.RunScript.InvalidExitCodeError(scriptpath, p.ExitCode));
|
||||
{
|
||||
if (!requiredScript)
|
||||
{
|
||||
// We log a warning or an error depending on the exit code
|
||||
switch (p.ExitCode)
|
||||
{
|
||||
case 0:
|
||||
case 1:
|
||||
// No messages here
|
||||
break;
|
||||
|
||||
case 2:
|
||||
case 3:
|
||||
Logging.Log.WriteWarningMessage(LOGTAG, "InvalidExitCode", null, Strings.RunScript.ExitCodeError(scriptpath, p.ExitCode, stderr));
|
||||
stderr = null;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
case 5:
|
||||
default:
|
||||
Logging.Log.WriteErrorMessage(LOGTAG, "InvalidExitCode", null, Strings.RunScript.ExitCodeError(scriptpath, p.ExitCode, stderr));
|
||||
stderr = null;
|
||||
break;
|
||||
}
|
||||
|
||||
// If this is the start event, we abort the backup
|
||||
if (eventname == "BEFORE")
|
||||
{
|
||||
switch (p.ExitCode)
|
||||
{
|
||||
case 1:
|
||||
throw new OperationAbortException(OperationAbortReason.Normal, Strings.RunScript.InvalidExitCodeError(scriptpath, p.ExitCode));
|
||||
case 3:
|
||||
throw new OperationAbortException(OperationAbortReason.Warning, Strings.RunScript.InvalidExitCodeError(scriptpath, p.ExitCode));
|
||||
case 5:
|
||||
throw new OperationAbortException(OperationAbortReason.Error, Strings.RunScript.InvalidExitCodeError(scriptpath, p.ExitCode));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
Logging.Log.WriteWarningMessage(LOGTAG, "InvalidExitCode", null, Strings.RunScript.InvalidExitCodeError(scriptpath, p.ExitCode));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logging.Log.WriteWarningMessage(LOGTAG, "ScriptTimeout", null, Strings.RunScript.ScriptTimeoutError(scriptpath));
|
||||
Logging.Log.WriteWarningMessage(LOGTAG, "ScriptTimeout", null, Strings.RunScript.ScriptTimeoutError(scriptpath));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,7 +310,7 @@ namespace Duplicati.Library.Modules.Builtin
|
||||
//We only allow setting parameters on startup
|
||||
if (eventname == "BEFORE" && stdout != null)
|
||||
{
|
||||
foreach(string rawline in stdout.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
|
||||
foreach (string rawline in stdout.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
string line = rawline.Trim();
|
||||
if (!line.StartsWith("--", StringComparison.Ordinal))
|
||||
@@ -303,7 +347,7 @@ namespace Duplicati.Library.Modules.Builtin
|
||||
localpath = value.Split(System.IO.Path.PathSeparator);
|
||||
}
|
||||
else if (
|
||||
string.Equals(key, "eventname", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(key, "eventname", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(key, "operationname", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(key, "main-action", StringComparison.OrdinalIgnoreCase) ||
|
||||
key == ""
|
||||
@@ -313,10 +357,14 @@ namespace Duplicati.Library.Modules.Builtin
|
||||
}
|
||||
else
|
||||
options[key] = value;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationAbortException)
|
||||
{
|
||||
// Do not log this, it is already logged
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.Log.WriteWarningMessage(LOGTAG, "ScriptExecuteError", ex, Strings.RunScript.ScriptExecuteError(scriptpath, ex.Message));
|
||||
@@ -325,49 +373,29 @@ namespace Duplicati.Library.Modules.Builtin
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper class to extract output from the program
|
||||
/// </summary>
|
||||
private class ConsoleDataHandler
|
||||
{
|
||||
public ConsoleDataHandler(System.Diagnostics.Process p)
|
||||
{
|
||||
p.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler(HandleOutputDataReceived);
|
||||
p.ErrorDataReceived += new System.Diagnostics.DataReceivedEventHandler(HandleErrorDataReceived);
|
||||
|
||||
p.BeginErrorReadLine();
|
||||
p.BeginOutputReadLine();
|
||||
m_task = Task.WhenAll(
|
||||
Task.Run(async () => StandardOutput = await p.StandardOutput.ReadToEndAsync()),
|
||||
Task.Run(async () => StandardError = await p.StandardError.ReadToEndAsync())
|
||||
);
|
||||
}
|
||||
|
||||
private readonly StringBuilder m_standardOutput = new StringBuilder();
|
||||
private readonly StringBuilder m_standardError = new StringBuilder();
|
||||
private readonly object m_lock = new object();
|
||||
|
||||
private void HandleOutputDataReceived (object sender, System.Diagnostics.DataReceivedEventArgs e)
|
||||
private readonly Task m_task;
|
||||
public string StandardOutput { get; private set; }
|
||||
public string StandardError { get; private set; }
|
||||
public void WaitForCompletion()
|
||||
{
|
||||
lock(m_lock)
|
||||
m_standardOutput.AppendLine(e.Data);
|
||||
}
|
||||
|
||||
private void HandleErrorDataReceived (object sender, System.Diagnostics.DataReceivedEventArgs e)
|
||||
{
|
||||
lock(m_lock)
|
||||
m_standardError.AppendLine(e.Data);
|
||||
}
|
||||
|
||||
public string StandardOutput
|
||||
{
|
||||
get
|
||||
{
|
||||
lock(m_lock)
|
||||
return m_standardOutput.ToString().Trim();
|
||||
}
|
||||
}
|
||||
|
||||
public string StandardError
|
||||
{
|
||||
get
|
||||
{
|
||||
lock(m_lock)
|
||||
return m_standardError.ToString().Trim();
|
||||
}
|
||||
// NOTE: This is ugly, but there is a race where "HasExited" is set,
|
||||
// but the stdout/stderr streams have not yet completed.
|
||||
// If we wait a little here, we eventually get the data.
|
||||
// If the streams have completed we do not wait.
|
||||
m_task.Wait(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ namespace Duplicati.Library.Modules.Builtin.Strings {
|
||||
public static string FinishoptionLong { get { return LC.L(@"Executes a script after performing an operation. The script will receive the operation results written to stdout."); } }
|
||||
public static string FinishoptionShort { get { return LC.L(@"Run a script on exit"); } }
|
||||
public static string InvalidExitCodeError(string script, int exitcode) { return LC.L(@"The script ""{0}"" returned with exit code {1}", script, exitcode); }
|
||||
public static string ExitCodeError(string script, int exitcode, string message) { return LC.L(@"The script ""{0}"" returned with exit code {1}{2}", script, exitcode, string.IsNullOrWhiteSpace(message) ? string.Empty : string.Format(": {0}", message)); }
|
||||
public static string RequiredoptionLong { get { return LC.L(@"Executes a script before performing an operation. The operation will block until the script has completed or timed out. If the script returns a non-zero error code or times out, the operation will be aborted."); } }
|
||||
public static string RequiredoptionShort { get { return LC.L(@"Run a required script on startup"); } }
|
||||
public static string ResultFormatShort { get { return LC.L(@"Selects the output format for results"); } }
|
||||
|
||||
@@ -16,6 +16,15 @@ REM --run-script-before = <filename>
|
||||
REM Duplicati will run the script before the backup job and waits for its
|
||||
REM completion for 60 seconds (default timeout value). After a timeout a
|
||||
REM warning is logged and the backup is started.
|
||||
REM The following exit codes are supported:
|
||||
REM
|
||||
REM - 0: OK, run operation
|
||||
REM - 1: OK, don't run operation
|
||||
REM - 2: Warning, run operation
|
||||
REM - 3: Warning, don't run operation
|
||||
REM - 4: Error, run operation
|
||||
REM - 5: Error don't run operation
|
||||
REM - other: Error don't run operation
|
||||
REM
|
||||
REM --run-script-before-required = <filename>
|
||||
REM Duplicati will run the script before the backup job and wait for its
|
||||
@@ -32,6 +41,10 @@ REM --run-script-after = <filename>
|
||||
REM Duplicati will run the script after the backup job and wait for its
|
||||
REM completion for 60 seconds (default timeout value). After a timeout a
|
||||
REM warning is logged.
|
||||
REM The same exit codes as in --run-script-before are supported, but
|
||||
REM the operation will always continue (i.e. 1 => 0, 3 => 2, 5 => 4)
|
||||
REM as it has already completed so stopping it during stop is useless.
|
||||
|
||||
|
||||
|
||||
REM ###############################################################################
|
||||
|
||||
@@ -16,6 +16,15 @@
|
||||
# Duplicati will run the script before the backup job and waits for its
|
||||
# completion for 60 seconds (default timeout value). After a timeout a
|
||||
# warning is logged and the backup is started.
|
||||
# The following exit codes are supported:
|
||||
#
|
||||
# - 0: OK, run operation
|
||||
# - 1: OK, don't run operation
|
||||
# - 2: Warning, run operation
|
||||
# - 3: Warning, don't run operation
|
||||
# - 4: Error, run operation
|
||||
# - 5: Error don't run operation
|
||||
# - other: Error don't run operation
|
||||
#
|
||||
# --run-script-before-required = <filename>
|
||||
# Duplicati will run the script before the backup job and wait for its
|
||||
@@ -32,6 +41,9 @@
|
||||
# Duplicati will run the script after the backup job and wait for its
|
||||
# completion for 60 seconds (default timeout value). After a timeout a
|
||||
# warning is logged.
|
||||
# The same exit codes as in --run-script-before are supported, but
|
||||
# the operation will always continue (i.e. 1 => 0, 3 => 2, 5 => 4)
|
||||
# as it has already completed so stopping it during stop is useless.
|
||||
|
||||
|
||||
###############################################################################
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
<Compile Include="ResultFormatSerializerProviderTest.cs" />
|
||||
<Compile Include="UriUtilityTests.cs" />
|
||||
<Compile Include="WebApiTests.cs" />
|
||||
<Compile Include="RunScriptTests.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
<ItemGroup>
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
// 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.Interface;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Duplicati.UnitTest
|
||||
{
|
||||
public class RunScriptTests : BasicSetupHelper
|
||||
{
|
||||
public override void PrepareSourceData()
|
||||
{
|
||||
base.PrepareSourceData();
|
||||
|
||||
Directory.CreateDirectory(DATAFOLDER);
|
||||
Directory.CreateDirectory(TARGETFOLDER);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Category("Border")]
|
||||
public void RunScriptBefore()
|
||||
{
|
||||
PrepareSourceData();
|
||||
|
||||
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 });
|
||||
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?");
|
||||
|
||||
System.Threading.Thread.Sleep(PAUSE_TIME);
|
||||
options["run-script-before"] = CreateScript(5);
|
||||
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?");
|
||||
|
||||
|
||||
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?");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private string CreateScript(int exitcode, string stderr = null, string stdout = null, int sleeptime = 0)
|
||||
{
|
||||
var id = Guid.NewGuid().ToString("N").Substring(0, 6);
|
||||
if (Library.Utility.Utility.IsClientWindows)
|
||||
{
|
||||
var commands = 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 (!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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user