2023-04-14 10:37:05 +00:00
|
|
|
|
using DistTestCore.Codex;
|
|
|
|
|
using DistTestCore.Logs;
|
|
|
|
|
using DistTestCore.Marketplace;
|
2023-04-13 13:02:51 +00:00
|
|
|
|
using DistTestCore.Metrics;
|
2023-04-14 12:53:39 +00:00
|
|
|
|
using KubernetesWorkflow;
|
2023-04-14 10:37:05 +00:00
|
|
|
|
using Logging;
|
2023-04-13 09:53:54 +00:00
|
|
|
|
using NUnit.Framework;
|
2023-04-27 13:55:33 +00:00
|
|
|
|
using System.Reflection;
|
2023-04-14 12:53:39 +00:00
|
|
|
|
using Utils;
|
2023-04-12 14:06:04 +00:00
|
|
|
|
|
|
|
|
|
namespace DistTestCore
|
|
|
|
|
{
|
|
|
|
|
[SetUpFixture]
|
|
|
|
|
public abstract class DistTest
|
|
|
|
|
{
|
2023-04-14 12:53:39 +00:00
|
|
|
|
private readonly Configuration configuration = new Configuration();
|
2023-04-27 13:55:33 +00:00
|
|
|
|
private readonly Assembly[] testAssemblies;
|
2023-04-14 12:53:39 +00:00
|
|
|
|
private FixtureLog fixtureLog = null!;
|
2023-04-12 14:06:04 +00:00
|
|
|
|
private TestLifecycle lifecycle = null!;
|
2023-04-14 12:53:39 +00:00
|
|
|
|
private DateTime testStart = DateTime.MinValue;
|
2023-04-12 14:06:04 +00:00
|
|
|
|
|
2023-04-27 13:55:33 +00:00
|
|
|
|
public DistTest()
|
|
|
|
|
{
|
|
|
|
|
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
|
|
|
|
testAssemblies = assemblies.Where(a => a.FullName!.ToLowerInvariant().Contains("test")).ToArray();
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-12 14:06:04 +00:00
|
|
|
|
[OneTimeSetUp]
|
|
|
|
|
public void GlobalSetup()
|
|
|
|
|
{
|
|
|
|
|
// Previous test run may have been interrupted.
|
|
|
|
|
// Begin by cleaning everything up.
|
2023-04-27 13:55:33 +00:00
|
|
|
|
Timing.UseLongTimeouts = false;
|
2023-04-14 12:53:39 +00:00
|
|
|
|
fixtureLog = new FixtureLog(configuration.GetLogConfig());
|
2023-04-12 14:06:04 +00:00
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2023-04-14 12:53:39 +00:00
|
|
|
|
Stopwatch.Measure(fixtureLog, "Global setup", () =>
|
|
|
|
|
{
|
2023-04-25 09:31:15 +00:00
|
|
|
|
var wc = new WorkflowCreator(fixtureLog, configuration.GetK8sConfiguration());
|
2023-04-14 12:53:39 +00:00
|
|
|
|
wc.CreateWorkflow().DeleteAllResources();
|
|
|
|
|
});
|
2023-04-12 14:06:04 +00:00
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
GlobalTestFailure.HasFailed = true;
|
2023-04-17 14:28:07 +00:00
|
|
|
|
fixtureLog.Error($"Global setup cleanup failed with: {ex}");
|
2023-04-12 14:06:04 +00:00
|
|
|
|
throw;
|
|
|
|
|
}
|
2023-04-14 12:53:39 +00:00
|
|
|
|
|
|
|
|
|
fixtureLog.Log("Global setup cleanup successful");
|
2023-04-19 08:42:08 +00:00
|
|
|
|
fixtureLog.Log($"Codex image: '{CodexContainerRecipe.DockerImage}'");
|
|
|
|
|
fixtureLog.Log($"Prometheus image: '{PrometheusContainerRecipe.DockerImage}'");
|
|
|
|
|
fixtureLog.Log($"Geth image: '{GethContainerRecipe.DockerImage}'");
|
2023-04-12 14:06:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[SetUp]
|
|
|
|
|
public void SetUpDistTest()
|
|
|
|
|
{
|
2023-04-27 13:55:33 +00:00
|
|
|
|
Timing.UseLongTimeouts = ShouldUseLongTimeouts();
|
|
|
|
|
|
2023-04-12 14:06:04 +00:00
|
|
|
|
if (GlobalTestFailure.HasFailed)
|
|
|
|
|
{
|
|
|
|
|
Assert.Inconclusive("Skip test: Previous test failed during clean up.");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
CreateNewTestLifecycle();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[TearDown]
|
|
|
|
|
public void TearDownDistTest()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2023-04-14 12:53:39 +00:00
|
|
|
|
DisposeTestLifecycle();
|
2023-04-12 14:06:04 +00:00
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2023-04-17 14:28:07 +00:00
|
|
|
|
fixtureLog.Error("Cleanup failed: " + ex.Message);
|
2023-04-12 14:06:04 +00:00
|
|
|
|
GlobalTestFailure.HasFailed = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public TestFile GenerateTestFile(ByteSize size)
|
|
|
|
|
{
|
|
|
|
|
return lifecycle.FileManager.GenerateTestFile(size);
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-30 08:56:19 +00:00
|
|
|
|
public IOnlineCodexNode SetupCodexBootstrapNode()
|
|
|
|
|
{
|
|
|
|
|
return SetupCodexBootstrapNode(s => { });
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-01 09:14:42 +00:00
|
|
|
|
public virtual IOnlineCodexNode SetupCodexBootstrapNode(Action<ICodexSetup> setup)
|
2023-04-30 08:56:19 +00:00
|
|
|
|
{
|
|
|
|
|
return SetupCodexNode(s =>
|
|
|
|
|
{
|
|
|
|
|
setup(s);
|
|
|
|
|
s.WithName("Bootstrap");
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-25 10:52:11 +00:00
|
|
|
|
public IOnlineCodexNode SetupCodexNode()
|
2023-04-12 14:06:04 +00:00
|
|
|
|
{
|
2023-04-25 10:52:11 +00:00
|
|
|
|
return SetupCodexNode(s => { });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IOnlineCodexNode SetupCodexNode(Action<ICodexSetup> setup)
|
|
|
|
|
{
|
|
|
|
|
return SetupCodexNodes(1, setup)[0];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ICodexNodeGroup SetupCodexNodes(int numberOfNodes)
|
|
|
|
|
{
|
|
|
|
|
return SetupCodexNodes(numberOfNodes, s => { });
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-01 09:14:42 +00:00
|
|
|
|
public virtual ICodexNodeGroup SetupCodexNodes(int numberOfNodes, Action<ICodexSetup> setup)
|
2023-04-25 10:52:11 +00:00
|
|
|
|
{
|
|
|
|
|
var codexSetup = new CodexSetup(numberOfNodes);
|
|
|
|
|
|
|
|
|
|
setup(codexSetup);
|
|
|
|
|
|
|
|
|
|
return BringOnline(codexSetup);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ICodexNodeGroup BringOnline(ICodexSetup codexSetup)
|
|
|
|
|
{
|
|
|
|
|
return lifecycle.CodexStarter.BringOnline((CodexSetup)codexSetup);
|
2023-04-12 14:06:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-04-26 12:40:54 +00:00
|
|
|
|
protected BaseLog Log
|
|
|
|
|
{
|
|
|
|
|
get { return lifecycle.Log; }
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-01 09:14:42 +00:00
|
|
|
|
private bool ShouldUseLongTimeouts()
|
|
|
|
|
{
|
|
|
|
|
// Don't be fooled! TestContext.CurrentTest.Test allows you easy access to the attributes of the current test.
|
|
|
|
|
// But this doesn't work for tests making use of [TestCase]. So instead, we use reflection here to figure out
|
|
|
|
|
// if the attribute is present.
|
|
|
|
|
var currentTest = TestContext.CurrentContext.Test;
|
|
|
|
|
var className = currentTest.ClassName;
|
|
|
|
|
var methodName = currentTest.MethodName;
|
|
|
|
|
|
|
|
|
|
var testClasses = testAssemblies.SelectMany(a => a.GetTypes()).Where(c => c.FullName == className).ToArray();
|
|
|
|
|
var testMethods = testClasses.SelectMany(c => c.GetMethods()).Where(m => m.Name == methodName).ToArray();
|
|
|
|
|
|
|
|
|
|
return testMethods.Any(m => m.GetCustomAttribute<UseLongTimeoutsAttribute>() != null);
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-12 14:06:04 +00:00
|
|
|
|
private void CreateNewTestLifecycle()
|
|
|
|
|
{
|
2023-05-03 12:18:37 +00:00
|
|
|
|
var testName = GetCurrentTestName();
|
|
|
|
|
Stopwatch.Measure(fixtureLog, $"Setup for {testName}", () =>
|
2023-04-14 12:53:39 +00:00
|
|
|
|
{
|
|
|
|
|
lifecycle = new TestLifecycle(fixtureLog.CreateTestLog(), configuration);
|
|
|
|
|
testStart = DateTime.UtcNow;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void DisposeTestLifecycle()
|
|
|
|
|
{
|
|
|
|
|
fixtureLog.Log($"{GetCurrentTestName()} = {GetTestResult()} ({GetTestDuration()})");
|
|
|
|
|
Stopwatch.Measure(fixtureLog, $"Teardown for {GetCurrentTestName()}", () =>
|
|
|
|
|
{
|
|
|
|
|
lifecycle.Log.EndTest();
|
|
|
|
|
IncludeLogsAndMetricsOnTestFailure();
|
|
|
|
|
lifecycle.DeleteAllResources();
|
|
|
|
|
lifecycle = null!;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-17 14:28:07 +00:00
|
|
|
|
private void IncludeLogsAndMetricsOnTestFailure()
|
|
|
|
|
{
|
|
|
|
|
var result = TestContext.CurrentContext.Result;
|
|
|
|
|
if (result.Outcome.Status == NUnit.Framework.Interfaces.TestStatus.Failed)
|
|
|
|
|
{
|
|
|
|
|
fixtureLog.MarkAsFailed();
|
|
|
|
|
|
|
|
|
|
if (IsDownloadingLogsAndMetricsEnabled())
|
|
|
|
|
{
|
|
|
|
|
lifecycle.Log.Log("Downloading all CodexNode logs and metrics because of test failure...");
|
|
|
|
|
DownloadAllLogs();
|
|
|
|
|
DownloadAllMetrics();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
lifecycle.Log.Log("Skipping download of all CodexNode logs and metrics due to [DontDownloadLogsAndMetricsOnFailure] attribute.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-14 12:53:39 +00:00
|
|
|
|
private string GetTestDuration()
|
|
|
|
|
{
|
|
|
|
|
var testDuration = DateTime.UtcNow - testStart;
|
|
|
|
|
return Time.FormatDuration(testDuration);
|
2023-04-12 14:06:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-04-13 09:53:54 +00:00
|
|
|
|
private void DownloadAllLogs()
|
2023-04-12 14:06:04 +00:00
|
|
|
|
{
|
2023-04-13 13:02:51 +00:00
|
|
|
|
OnEachCodexNode(node =>
|
2023-04-13 09:53:54 +00:00
|
|
|
|
{
|
|
|
|
|
lifecycle.DownloadLog(node);
|
2023-04-13 13:02:51 +00:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void DownloadAllMetrics()
|
|
|
|
|
{
|
|
|
|
|
var metricsDownloader = new MetricsDownloader(lifecycle.Log);
|
|
|
|
|
|
|
|
|
|
OnEachCodexNode(node =>
|
|
|
|
|
{
|
2023-04-13 13:04:01 +00:00
|
|
|
|
var m = node.Metrics as MetricsAccess;
|
|
|
|
|
if (m != null)
|
|
|
|
|
{
|
|
|
|
|
metricsDownloader.DownloadAllMetricsForNode(node.GetName(), m);
|
|
|
|
|
}
|
2023-04-13 13:02:51 +00:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OnEachCodexNode(Action<OnlineCodexNode> action)
|
|
|
|
|
{
|
|
|
|
|
var allNodes = lifecycle.CodexStarter.RunningGroups.SelectMany(g => g.Nodes);
|
|
|
|
|
foreach (var node in allNodes)
|
|
|
|
|
{
|
|
|
|
|
action(node);
|
2023-04-13 09:53:54 +00:00
|
|
|
|
}
|
2023-04-12 14:06:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-04-14 12:53:39 +00:00
|
|
|
|
private string GetCurrentTestName()
|
|
|
|
|
{
|
|
|
|
|
return $"[{TestContext.CurrentContext.Test.Name}]";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string GetTestResult()
|
|
|
|
|
{
|
|
|
|
|
return TestContext.CurrentContext.Result.Outcome.Status.ToString();
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-13 09:53:54 +00:00
|
|
|
|
private bool IsDownloadingLogsAndMetricsEnabled()
|
|
|
|
|
{
|
|
|
|
|
var testProperties = TestContext.CurrentContext.Test.Properties;
|
|
|
|
|
return !testProperties.ContainsKey(DontDownloadLogsAndMetricsOnFailureAttribute.DontDownloadKey);
|
|
|
|
|
}
|
2023-04-12 14:06:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static class GlobalTestFailure
|
|
|
|
|
{
|
|
|
|
|
public static bool HasFailed { get; set; } = false;
|
|
|
|
|
}
|
|
|
|
|
}
|