[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);
|
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 ()
|
public int Run ()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
@ -559,8 +539,13 @@ namespace Xharness.Jenkins {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!string.IsNullOrEmpty (Harness.PeriodicCommand)) {
|
if (!string.IsNullOrEmpty (Harness.PeriodicCommand)) {
|
||||||
var periodic_log = Logs.Create ("PeriodicCommand.log", "Periodic command log");
|
var periodicCommand = new PeriodicCommand (
|
||||||
Task.Run (async () => await ExecutePeriodicCommandAsync (periodic_log));
|
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.
|
// 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="Tests\MacFlavorsExtensionsTests.cs" />
|
||||||
<Compile Include="Jenkins\MakeTestTaskEnumerableTests.cs" />
|
<Compile Include="Jenkins\MakeTestTaskEnumerableTests.cs" />
|
||||||
<Compile Include="Jenkins\NUnitTestTasksEnumerableTests.cs" />
|
<Compile Include="Jenkins\NUnitTestTasksEnumerableTests.cs" />
|
||||||
|
<Compile Include="Jenkins\PeriodicCommandTests.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
|
|
|
@ -136,6 +136,7 @@
|
||||||
<Compile Include="MacFlavorsExtensions.cs" />
|
<Compile Include="MacFlavorsExtensions.cs" />
|
||||||
<Compile Include="Jenkins\NUnitTestTasksEnumerable.cs" />
|
<Compile Include="Jenkins\NUnitTestTasksEnumerable.cs" />
|
||||||
<Compile Include="Jenkins\MakeTestTaskEnumerable.cs" />
|
<Compile Include="Jenkins\MakeTestTaskEnumerable.cs" />
|
||||||
|
<Compile Include="Jenkins\PeriodicCommand.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="..\..\tools\common\SdkVersions.cs">
|
<Compile Include="..\..\tools\common\SdkVersions.cs">
|
||||||
|
|
Загрузка…
Ссылка в новой задаче