cs-codex-dist-tests/Tests/CodexContinuousTests/SingleTestRun.cs

261 lines
9.0 KiB
C#
Raw Normal View History

2023-09-20 11:33:58 +00:00
using Logging;
using Utils;
2023-06-23 08:14:16 +00:00
using KubernetesWorkflow;
using NUnit.Framework.Internal;
2023-06-27 08:16:59 +00:00
using System.Reflection;
2023-09-20 11:33:58 +00:00
using CodexPlugin;
using DistTestCore.Logs;
using Core;
using System.ComponentModel;
namespace ContinuousTests
{
public class SingleTestRun
{
2023-06-25 07:53:10 +00:00
private readonly List<Exception> exceptions = new List<Exception>();
2023-09-20 11:33:58 +00:00
private readonly EntryPoint entryPoint;
2023-06-28 14:19:37 +00:00
private readonly TaskFactory taskFactory;
private readonly Configuration config;
2023-09-21 08:33:09 +00:00
private readonly ILog overviewLog;
2023-06-25 07:53:10 +00:00
private readonly TestHandle handle;
2023-06-28 14:19:37 +00:00
private readonly CancellationToken cancelToken;
2023-09-20 11:33:58 +00:00
private readonly ICodexNode[] nodes;
2023-06-25 07:53:10 +00:00
private readonly FixtureLog fixtureLog;
2023-06-25 09:24:32 +00:00
private readonly string testName;
private static int failureCount = 0;
2023-09-21 08:33:09 +00:00
public SingleTestRun(EntryPointFactory entryPointFactory, TaskFactory taskFactory, Configuration config, ILog overviewLog, TestHandle handle, StartupChecker startupChecker, CancellationToken cancelToken)
{
2023-06-28 14:19:37 +00:00
this.taskFactory = taskFactory;
this.config = config;
2023-06-25 09:06:47 +00:00
this.overviewLog = overviewLog;
2023-06-25 07:53:10 +00:00
this.handle = handle;
2023-06-28 14:19:37 +00:00
this.cancelToken = cancelToken;
2023-06-25 09:24:32 +00:00
testName = handle.Test.GetType().Name;
fixtureLog = new FixtureLog(new LogConfig(config.LogPath, false), DateTime.UtcNow, testName);
2023-09-21 08:33:09 +00:00
entryPoint = entryPointFactory.CreateEntryPoint(config.KubeConfigFile, config.DataPath, config.CodexDeployment.Metadata.KubeNamespace, fixtureLog);
ApplyLogReplacements(fixtureLog, startupChecker);
nodes = CreateRandomNodes();
}
2023-09-28 07:39:15 +00:00
public void Run(EventWaitHandle runFinishedHandle, Action<bool> resultHandler)
{
2023-06-28 14:19:37 +00:00
taskFactory.Run(() =>
2023-06-25 07:53:10 +00:00
{
try
{
2023-09-28 07:39:15 +00:00
RunTest(resultHandler);
2023-09-21 08:33:09 +00:00
entryPoint.Decommission(
deleteKubernetesResources: false, // This would delete the continuous test net.
deleteTrackedFiles: true
);
runFinishedHandle.Set();
2023-06-25 07:53:10 +00:00
}
catch (Exception ex)
{
2023-06-27 08:16:59 +00:00
overviewLog.Error("Test infra failure: SingleTestRun failed with " + ex);
Environment.Exit(-1);
2023-06-25 07:53:10 +00:00
}
});
}
2023-09-28 07:39:15 +00:00
private void RunTest(Action<bool> resultHandler)
2023-06-27 08:16:59 +00:00
{
2023-10-01 07:57:32 +00:00
var ci = entryPoint.CreateInterface();
var testStart = DateTime.UtcNow;
2023-06-27 08:16:59 +00:00
try
{
RunTestMoments();
var duration = DateTime.UtcNow - testStart;
OverviewLog($" > Test passed. ({Time.FormatDuration(duration)})");
2023-10-01 07:57:32 +00:00
if (!config.KeepPassedTestLogs)
{
fixtureLog.Delete();
}
2023-09-28 07:39:15 +00:00
resultHandler(true);
2023-06-27 08:16:59 +00:00
}
catch (Exception ex)
{
fixtureLog.Error("Test run failed with exception: " + ex);
fixtureLog.MarkAsFailed();
DownloadContainerLogs(testStart);
failureCount++;
2023-09-28 07:39:15 +00:00
resultHandler(false);
if (config.StopOnFailure > 0)
{
OverviewLog($"Failures: {failureCount} / {config.StopOnFailure}");
if (failureCount >= config.StopOnFailure)
{
2023-10-01 07:57:32 +00:00
OverviewLog($"Configured to stop after {config.StopOnFailure} failures.");
Cancellation.Cts.Cancel();
}
}
2023-06-27 08:16:59 +00:00
}
}
private void DownloadContainerLogs(DateTime testStart)
{
// The test failed just now. We can't expect the logs to be available in elastic-search immediately:
Thread.Sleep(TimeSpan.FromMinutes(1));
var effectiveStart = testStart.Subtract(TimeSpan.FromSeconds(10));
var effectiveEnd = DateTime.UtcNow.AddSeconds(30);
var elasticSearchLogDownloader = new ElasticSearchLogDownloader(entryPoint.Tools, fixtureLog);
foreach (var node in nodes)
2023-10-01 08:52:05 +00:00
{
var container = node.Container;
elasticSearchLogDownloader.Download(fixtureLog.CreateSubfile(), container, effectiveStart, effectiveEnd);
2023-10-01 08:52:05 +00:00
}
2023-06-27 08:16:59 +00:00
}
private void ApplyLogReplacements(FixtureLog fixtureLog, StartupChecker startupChecker)
{
foreach (var replacement in startupChecker.LogReplacements) fixtureLog.AddStringReplace(replacement.From, replacement.To);
}
2023-06-27 08:16:59 +00:00
private void RunTestMoments()
2023-06-25 07:53:10 +00:00
{
var earliestMoment = handle.GetEarliestMoment();
var t = earliestMoment;
2023-10-01 07:57:32 +00:00
while (!cancelToken.IsCancellationRequested)
2023-06-25 07:53:10 +00:00
{
RunMoment(t);
if (handle.Test.TestFailMode == TestFailMode.StopAfterFirstFailure && exceptions.Any())
{
Log("Exception detected. TestFailMode = StopAfterFirstFailure. Stopping...");
2023-06-27 08:16:59 +00:00
ThrowFailTest();
2023-06-25 07:53:10 +00:00
}
var nextMoment = handle.GetNextMoment(t);
if (nextMoment != null)
{
2023-06-28 14:19:37 +00:00
var delta = TimeSpan.FromSeconds(nextMoment.Value - t);
Log($" > Next TestMoment in {Time.FormatDuration(delta)} seconds...");
cancelToken.WaitHandle.WaitOne(delta);
t = nextMoment.Value;
2023-06-25 07:53:10 +00:00
}
else
{
2023-06-25 08:50:01 +00:00
if (exceptions.Any())
{
2023-06-27 08:16:59 +00:00
ThrowFailTest();
2023-06-25 08:50:01 +00:00
}
return;
2023-06-25 07:53:10 +00:00
}
}
2023-10-01 08:52:05 +00:00
fixtureLog.Log("Test run has been cancelled.");
2023-06-25 07:53:10 +00:00
}
2023-06-27 08:16:59 +00:00
private void ThrowFailTest()
{
var exs = UnpackExceptions(exceptions);
var exceptionsMessage = GetCombinedExceptionsMessage(exs);
Log(exceptionsMessage);
2023-09-01 06:45:23 +00:00
OverviewLog($" > Test failed: " + exceptionsMessage);
throw new Exception(exceptionsMessage);
2023-06-27 08:16:59 +00:00
}
private string GetCombinedExceptionsMessage(Exception[] exceptions)
{
return string.Join(Environment.NewLine, exceptions.Select(ex => ex.ToString()));
}
private Exception[] UnpackExceptions(List<Exception> exceptions)
{
return exceptions.Select(UnpackException).ToArray();
}
2023-06-27 08:16:59 +00:00
private Exception UnpackException(Exception exception)
{
if (exception is AggregateException a)
{
return UnpackException(a.InnerExceptions.First());
}
if (exception is TargetInvocationException t)
{
return UnpackException(t.InnerException!);
}
return exception;
}
2023-06-25 07:53:10 +00:00
private void RunMoment(int t)
{
using (var context = new TestExecutionContext.IsolatedContext())
2023-06-25 07:53:10 +00:00
{
try
{
handle.InvokeMoment(t, InitializeTest);
}
catch (Exception ex)
{
exceptions.Add(ex);
}
2023-06-25 07:53:10 +00:00
}
DecommissionTest();
}
private void InitializeTest(string name)
{
Log($" > Running TestMoment '{name}'");
2023-09-20 11:33:58 +00:00
handle.Test.Initialize(nodes, fixtureLog, entryPoint.Tools.GetFileManager(), config, cancelToken);
2023-10-03 13:11:45 +00:00
handle.Test.Tools = entryPoint.Tools;
2023-06-25 07:53:10 +00:00
}
private void DecommissionTest()
{
2023-06-28 14:19:37 +00:00
handle.Test.Initialize(null!, null!, null!, null!, cancelToken);
}
2023-06-25 07:53:10 +00:00
private void Log(string msg)
{
2023-06-25 07:53:10 +00:00
fixtureLog.Log(msg);
}
2023-06-25 09:06:47 +00:00
private void OverviewLog(string msg)
{
Log(msg);
var containerNames = GetContainerNames();
overviewLog.Log($"{containerNames} {testName}: {msg}");
2023-06-25 09:06:47 +00:00
}
private string GetContainerNames()
{
if (handle.Test.RequiredNumberOfNodes == -1) return "(All Nodes)";
return $"({string.Join(",", nodes.Select(n => n.Container.Name))})";
}
2023-09-20 11:33:58 +00:00
private ICodexNode[] CreateRandomNodes()
{
var containers = SelectRandomContainers();
2023-06-25 07:53:10 +00:00
fixtureLog.Log("Selected nodes: " + string.Join(",", containers.Select(c => c.Name)));
2023-09-20 11:33:58 +00:00
return entryPoint.CreateInterface().WrapCodexContainers(containers).ToArray();
}
private RunningContainer[] SelectRandomContainers()
{
var number = handle.Test.RequiredNumberOfNodes;
if (number == -1) return config.CodexDeployment.CodexContainers;
2023-06-23 08:14:16 +00:00
var containers = config.CodexDeployment.CodexContainers.ToList();
var result = new RunningContainer[number];
for (var i = 0; i < number; i++)
{
2023-06-23 08:14:16 +00:00
result[i] = containers.PickOneRandom();
}
return result;
}
}
}