[Harness] Move the periodic command execution logic out of Jenkins. (#8657)
Move the logic out, Jenkins class should just orchestrate all the diff tasks but should not know how they are executed.
This commit is contained in:
Родитель
98788f8558
Коммит
98016a760e
|
@ -517,26 +517,6 @@ namespace Xharness.Jenkins {
|
|||
return Task.WhenAll (loadsim, loaddev);
|
||||
}
|
||||
|
||||
async Task ExecutePeriodicCommandAsync (ILog periodic_loc)
|
||||
{
|
||||
periodic_loc.WriteLine ($"Starting periodic task with interval {Harness.PeriodicCommandInterval.TotalMinutes} minutes.");
|
||||
while (true) {
|
||||
var watch = Stopwatch.StartNew ();
|
||||
using (var process = new Process ()) {
|
||||
process.StartInfo.FileName = Harness.PeriodicCommand;
|
||||
process.StartInfo.Arguments = Harness.PeriodicCommandArguments;
|
||||
var rv = await processManager.RunAsync (process, periodic_loc, timeout: Harness.PeriodicCommandInterval);
|
||||
if (!rv.Succeeded)
|
||||
periodic_loc.WriteLine ($"Periodic command failed with exit code {rv.ExitCode} (Timed out: {rv.TimedOut})");
|
||||
}
|
||||
var ticksLeft = watch.ElapsedTicks - Harness.PeriodicCommandInterval.Ticks;
|
||||
if (ticksLeft < 0)
|
||||
ticksLeft = Harness.PeriodicCommandInterval.Ticks;
|
||||
var wait = TimeSpan.FromTicks (ticksLeft);
|
||||
await Task.Delay (wait);
|
||||
}
|
||||
}
|
||||
|
||||
public int Run ()
|
||||
{
|
||||
try {
|
||||
|
@ -559,8 +539,13 @@ namespace Xharness.Jenkins {
|
|||
});
|
||||
}
|
||||
if (!string.IsNullOrEmpty (Harness.PeriodicCommand)) {
|
||||
var periodic_log = Logs.Create ("PeriodicCommand.log", "Periodic command log");
|
||||
Task.Run (async () => await ExecutePeriodicCommandAsync (periodic_log));
|
||||
var periodicCommand = new PeriodicCommand (
|
||||
command: Harness.PeriodicCommand,
|
||||
processManager: processManager,
|
||||
interval: Harness.PeriodicCommandInterval,
|
||||
logs: logs,
|
||||
arguments: string.IsNullOrEmpty (Harness.PeriodicCommandArguments) ? null : Harness.PeriodicCommandArguments);
|
||||
periodicCommand.Execute ().DoNotAwait ();
|
||||
}
|
||||
|
||||
// We can populate and build test-libraries in parallel.
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.DotNet.XHarness.iOS.Shared.Execution;
|
||||
using Microsoft.DotNet.XHarness.iOS.Shared.Logging;
|
||||
|
||||
#nullable enable
|
||||
namespace Xharness.Jenkins {
|
||||
|
||||
/// <summary>
|
||||
/// Represent a command that will be executed periodically.
|
||||
/// </summary>
|
||||
class PeriodicCommand {
|
||||
|
||||
readonly string command;
|
||||
readonly string? arguments;
|
||||
readonly TimeSpan interval;
|
||||
readonly IProcessManager processManager;
|
||||
readonly ILog log;
|
||||
|
||||
public PeriodicCommand (string command, IProcessManager processManager, TimeSpan interval, ILogs logs, string? arguments = null)
|
||||
{
|
||||
if (logs == null)
|
||||
throw new ArgumentNullException (nameof (logs));
|
||||
|
||||
this.log = logs.Create ("PeriodicCommand.log", "Periodic command log");
|
||||
this.command = command ?? throw new ArgumentNullException (nameof (command));
|
||||
this.processManager = processManager ?? throw new ArgumentNullException (nameof (processManager));
|
||||
this.interval = interval;
|
||||
this.arguments = arguments;
|
||||
}
|
||||
|
||||
async Task ExecuteInternal (CancellationToken? cancellationToken = null)
|
||||
{
|
||||
log.WriteLine ($"Starting periodic task with interval {interval.TotalMinutes} minutes.");
|
||||
while (true) {
|
||||
var watch = Stopwatch.StartNew ();
|
||||
using (var process = new Process ()) {
|
||||
process.StartInfo.FileName = command;
|
||||
process.StartInfo.Arguments = arguments;
|
||||
ProcessExecutionResult? rv = cancellationToken.HasValue
|
||||
? await processManager.RunAsync (process, log, timeout: interval, cancellation_token: cancellationToken)
|
||||
: await processManager.RunAsync (process, log, timeout: interval);
|
||||
if (rv != null && !rv.Succeeded)
|
||||
log.WriteLine ($"Periodic command failed with exit code {rv.ExitCode} (Timed out: {rv.TimedOut})");
|
||||
}
|
||||
var ticksLeft = watch.ElapsedTicks - interval.Ticks;
|
||||
if (ticksLeft < 0)
|
||||
ticksLeft = interval.Ticks;
|
||||
var wait = TimeSpan.FromTicks (ticksLeft);
|
||||
await Task.Delay (wait);
|
||||
}
|
||||
}
|
||||
|
||||
public Task Execute (CancellationToken? cancellationToken = null)
|
||||
=> cancellationToken != null
|
||||
? Task.Run (async () => await ExecuteInternal (cancellationToken.Value), cancellationToken.Value)
|
||||
: Task.Run (async () => await ExecuteInternal ());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.DotNet.XHarness.iOS.Shared.Execution;
|
||||
using Microsoft.DotNet.XHarness.iOS.Shared.Logging;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Xharness.Jenkins;
|
||||
|
||||
namespace Xharness.Tests.Jenkins {
|
||||
|
||||
[TestFixture]
|
||||
public class PeriodicCommandTests {
|
||||
|
||||
Mock<IProcessManager> processManager;
|
||||
Mock<ILogs> logs;
|
||||
Mock<ILog> log;
|
||||
TimeSpan interval;
|
||||
string command;
|
||||
string arguments;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp ()
|
||||
{
|
||||
processManager = new Mock<IProcessManager> (MockBehavior.Strict);
|
||||
logs = new Mock<ILogs> ();
|
||||
log = new Mock<ILog> ();
|
||||
|
||||
// common setup for the mocks
|
||||
logs.Setup (l => l.Create (It.Is<string> (s => true), It.Is<string> (s => true), null)).Returns (log.Object);
|
||||
interval = TimeSpan.FromMilliseconds (100);
|
||||
command = "test";
|
||||
arguments = "periodic";
|
||||
}
|
||||
|
||||
// we do not test the options without the cancellation task because we want to be nice people when running
|
||||
// the tests and do not leave a thread doing nothing
|
||||
[Test]
|
||||
public async Task TestExecuteNoArgs ()
|
||||
{
|
||||
var periodicCommand = new PeriodicCommand (command, processManager.Object, interval, logs.Object);
|
||||
var executionTcs = new TaskCompletionSource<bool> ();
|
||||
var threadCs = new CancellationTokenSource ();
|
||||
|
||||
processManager.Setup (pm => pm.RunAsync (
|
||||
It.Is<Process> (p => p.StartInfo.FileName == command && p.StartInfo.Arguments == string.Empty),
|
||||
It.IsAny<ILog> (),
|
||||
interval,
|
||||
null,
|
||||
It.IsAny<CancellationToken> (),
|
||||
null)).Callback<Process, ILog, TimeSpan?, Dictionary<string, string>, CancellationToken?, bool?> (
|
||||
(p, l, i, env, t, d) => {
|
||||
executionTcs.TrySetResult (true);
|
||||
}).ReturnsAsync (new ProcessExecutionResult { ExitCode = 0, TimedOut = false }).Verifiable ();
|
||||
|
||||
var task = periodicCommand.Execute (threadCs.Token);
|
||||
await executionTcs.Task; // wait for the callback in the mock, which is in another thread to set the source
|
||||
processManager.VerifyAll ();
|
||||
processManager.VerifyNoOtherCalls ();
|
||||
threadCs.Cancel (); // clean
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestExecuteArgs ()
|
||||
{
|
||||
// all similar logic to the above one, but with arguments
|
||||
var periodicCommand = new PeriodicCommand (command, processManager.Object, interval, logs.Object, arguments: arguments);
|
||||
var executionTcs = new TaskCompletionSource<bool> ();
|
||||
var threadCs = new CancellationTokenSource ();
|
||||
|
||||
processManager.Setup (pm => pm.RunAsync (
|
||||
It.Is<Process> (p => p.StartInfo.FileName == command && p.StartInfo.Arguments == arguments),
|
||||
It.IsAny<ILog> (),
|
||||
interval,
|
||||
null,
|
||||
It.IsAny<CancellationToken> (),
|
||||
null)).Callback<Process, ILog, TimeSpan?, Dictionary<string, string>, CancellationToken?, bool?> (
|
||||
(p, l, i, env, t, d) => {
|
||||
executionTcs.TrySetResult (true);
|
||||
}).ReturnsAsync (new ProcessExecutionResult { ExitCode = 0, TimedOut = false }).Verifiable ();
|
||||
|
||||
var task = periodicCommand.Execute (threadCs.Token);
|
||||
await executionTcs.Task; // wait for the callback in the mock, which is in another thread to set the source
|
||||
processManager.VerifyAll ();
|
||||
processManager.VerifyNoOtherCalls ();
|
||||
threadCs.Cancel (); // clean
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -68,6 +68,7 @@
|
|||
<Compile Include="Tests\MacFlavorsExtensionsTests.cs" />
|
||||
<Compile Include="Jenkins\MakeTestTaskEnumerableTests.cs" />
|
||||
<Compile Include="Jenkins\NUnitTestTasksEnumerableTests.cs" />
|
||||
<Compile Include="Jenkins\PeriodicCommandTests.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
|
|
|
@ -136,6 +136,7 @@
|
|||
<Compile Include="MacFlavorsExtensions.cs" />
|
||||
<Compile Include="Jenkins\NUnitTestTasksEnumerable.cs" />
|
||||
<Compile Include="Jenkins\MakeTestTaskEnumerable.cs" />
|
||||
<Compile Include="Jenkins\PeriodicCommand.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\tools\common\SdkVersions.cs">
|
||||
|
|
Загрузка…
Ссылка в новой задаче