[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:
Manuel de la Pena 2020-05-25 10:00:57 -04:00 коммит произвёл GitHub
Родитель 98788f8558
Коммит 98016a760e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
5 изменённых файлов: 162 добавлений и 22 удалений

Просмотреть файл

@ -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">