2023-09-12 13:32:06 +02:00
|
|
|
|
using Core;
|
2023-09-20 10:51:47 +02:00
|
|
|
|
using DistTestCore.Logs;
|
2023-09-11 10:43:27 +02:00
|
|
|
|
using FileUtils;
|
2023-04-14 12:37:05 +02:00
|
|
|
|
using Logging;
|
2023-04-13 11:53:54 +02:00
|
|
|
|
using NUnit.Framework;
|
2024-03-27 15:01:32 +01:00
|
|
|
|
using NUnit.Framework.Interfaces;
|
2023-04-27 15:55:33 +02:00
|
|
|
|
using System.Reflection;
|
2023-09-08 10:21:40 +02:00
|
|
|
|
using Utils;
|
2025-01-16 13:51:29 +01:00
|
|
|
|
using WebUtils;
|
2023-09-20 10:51:47 +02:00
|
|
|
|
using Assert = NUnit.Framework.Assert;
|
2023-04-12 16:06:04 +02:00
|
|
|
|
|
|
|
|
|
|
namespace DistTestCore
|
|
|
|
|
|
{
|
2023-05-04 08:25:48 +02:00
|
|
|
|
[Parallelizable(ParallelScope.All)]
|
2025-04-25 15:42:13 +02:00
|
|
|
|
[FixtureLifeCycle(LifeCycle.InstancePerTestCase)]
|
2023-09-13 08:55:04 +02:00
|
|
|
|
public abstract class DistTest
|
2023-04-12 16:06:04 +02:00
|
|
|
|
{
|
2025-04-25 15:42:13 +02:00
|
|
|
|
private static readonly Global global = new Global();
|
2023-05-03 14:55:26 +02:00
|
|
|
|
private readonly FixtureLog fixtureLog;
|
2023-07-18 09:47:44 +02:00
|
|
|
|
private readonly StatusLog statusLog;
|
2025-04-25 15:42:13 +02:00
|
|
|
|
private readonly TestLifecycle lifecycle;
|
|
|
|
|
|
private readonly string deployId = NameUtils.MakeDeployId();
|
|
|
|
|
|
|
2023-04-27 15:55:33 +02:00
|
|
|
|
public DistTest()
|
|
|
|
|
|
{
|
2025-04-25 15:42:13 +02:00
|
|
|
|
var logConfig = global.Configuration.GetLogConfig();
|
2023-07-18 09:47:44 +02:00
|
|
|
|
var startTime = DateTime.UtcNow;
|
2025-04-22 12:25:37 +02:00
|
|
|
|
fixtureLog = FixtureLog.Create(logConfig, startTime, deployId);
|
2024-02-22 10:41:07 -03:00
|
|
|
|
statusLog = new StatusLog(logConfig, startTime, "dist-tests", deployId);
|
2023-09-12 14:50:18 +02:00
|
|
|
|
|
2025-04-25 15:42:13 +02:00
|
|
|
|
fixtureLog.Log("Test framework revision: " + GitInfo.GetStatus());
|
|
|
|
|
|
|
|
|
|
|
|
lifecycle = new TestLifecycle(fixtureLog.CreateTestLog(startTime), global.Configuration,
|
|
|
|
|
|
GetWebCallTimeSet(),
|
|
|
|
|
|
GetK8sTimeSet(),
|
|
|
|
|
|
Global.TestNamespacePrefix + Guid.NewGuid().ToString(),
|
|
|
|
|
|
deployId,
|
|
|
|
|
|
ShouldWaitForCleanup()
|
|
|
|
|
|
);
|
2023-09-28 12:22:35 +02:00
|
|
|
|
|
|
|
|
|
|
Initialize(fixtureLog);
|
2023-04-27 15:55:33 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-04-12 16:06:04 +02:00
|
|
|
|
[OneTimeSetUp]
|
2025-04-25 15:42:13 +02:00
|
|
|
|
public static void GlobalSetup()
|
2023-04-12 16:06:04 +02:00
|
|
|
|
{
|
2025-04-25 15:42:13 +02:00
|
|
|
|
global.Setup();
|
2023-04-12 16:06:04 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-09-12 10:31:55 +02:00
|
|
|
|
[OneTimeTearDown]
|
2025-04-25 15:42:13 +02:00
|
|
|
|
public static void GlobalTearDown()
|
2023-09-12 10:31:55 +02:00
|
|
|
|
{
|
2025-04-25 15:42:13 +02:00
|
|
|
|
global.TearDown();
|
2023-09-12 10:31:55 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-04-12 16:06:04 +02:00
|
|
|
|
[SetUp]
|
|
|
|
|
|
public void SetUpDistTest()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (GlobalTestFailure.HasFailed)
|
|
|
|
|
|
{
|
|
|
|
|
|
Assert.Inconclusive("Skip test: Previous test failed during clean up.");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[TearDown]
|
|
|
|
|
|
public void TearDownDistTest()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2023-04-14 14:53:39 +02:00
|
|
|
|
DisposeTestLifecycle();
|
2023-04-12 16:06:04 +02:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2024-04-15 11:37:14 +02:00
|
|
|
|
fixtureLog.Error("Cleanup failed: " + ex);
|
2023-04-12 16:06:04 +02:00
|
|
|
|
GlobalTestFailure.HasFailed = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-09-13 08:55:04 +02:00
|
|
|
|
public CoreInterface Ci
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
2025-04-25 15:42:13 +02:00
|
|
|
|
return lifecycle.CoreInterface;
|
2023-09-13 08:55:04 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-09-12 13:32:06 +02:00
|
|
|
|
public TrackedFile GenerateTestFile(ByteSize size, string label = "")
|
2023-04-12 16:06:04 +02:00
|
|
|
|
{
|
2025-04-25 15:42:13 +02:00
|
|
|
|
return lifecycle.GenerateTestFile(size, label);
|
2023-04-12 16:06:04 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-01 15:59:08 +02:00
|
|
|
|
public TrackedFile GenerateTestFile(Action<IGenerateOption> options, string label = "")
|
|
|
|
|
|
{
|
2025-04-25 15:42:13 +02:00
|
|
|
|
return lifecycle.GenerateTestFile(options, label);
|
2024-07-01 15:59:08 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-05-29 09:13:38 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Any test files generated in 'action' will be deleted after it returns.
|
|
|
|
|
|
/// This helps prevent large tests from filling up discs.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void ScopedTestFiles(Action action)
|
|
|
|
|
|
{
|
2025-04-25 15:42:13 +02:00
|
|
|
|
lifecycle.GetFileManager().ScopedFiles(action);
|
2023-05-29 09:13:38 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-09-12 10:31:55 +02:00
|
|
|
|
public ILog GetTestLog()
|
2023-05-10 09:55:36 +02:00
|
|
|
|
{
|
2025-04-25 15:42:13 +02:00
|
|
|
|
return lifecycle.Log;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public IFileManager GetFileManager()
|
|
|
|
|
|
{
|
|
|
|
|
|
return lifecycle.GetFileManager();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public string GetTestNamespace()
|
|
|
|
|
|
{
|
|
|
|
|
|
return lifecycle.TestNamespace;
|
2023-05-10 09:55:36 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-05-29 09:13:38 +02:00
|
|
|
|
public void Log(string msg)
|
2023-04-26 14:40:54 +02:00
|
|
|
|
{
|
2023-05-04 14:55:39 +02:00
|
|
|
|
TestContext.Progress.WriteLine(msg);
|
2023-05-10 09:55:36 +02:00
|
|
|
|
GetTestLog().Log(msg);
|
2023-05-04 14:55:39 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-05-29 09:13:38 +02:00
|
|
|
|
public void Debug(string msg)
|
2023-05-04 14:55:39 +02:00
|
|
|
|
{
|
|
|
|
|
|
TestContext.Progress.WriteLine(msg);
|
2023-05-10 09:55:36 +02:00
|
|
|
|
GetTestLog().Debug(msg);
|
2023-05-03 14:55:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-08-22 15:51:39 +02:00
|
|
|
|
public void Measure(string name, Action action)
|
|
|
|
|
|
{
|
2025-04-25 15:42:13 +02:00
|
|
|
|
Stopwatch.Measure(lifecycle.Log, name, action);
|
2023-08-22 15:51:39 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-20 10:55:29 +01:00
|
|
|
|
protected TimeRange GetTestRunTimeRange()
|
|
|
|
|
|
{
|
2025-04-29 12:09:05 +02:00
|
|
|
|
return new TimeRange(lifecycle.TestStartUtc, DateTime.UtcNow);
|
2023-12-20 10:55:29 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-09-28 12:22:35 +02:00
|
|
|
|
protected virtual void Initialize(FixtureLog fixtureLog)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-06 10:50:02 +01:00
|
|
|
|
protected virtual void CollectStatusLogData(TestLifecycle lifecycle, Dictionary<string, string> data)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-04-14 14:53:39 +02:00
|
|
|
|
private void DisposeTestLifecycle()
|
|
|
|
|
|
{
|
2023-07-21 09:20:28 +02:00
|
|
|
|
var testResult = GetTestResult();
|
|
|
|
|
|
var testDuration = lifecycle.GetTestDuration();
|
2023-12-06 10:50:02 +01:00
|
|
|
|
var data = lifecycle.GetPluginMetadata();
|
|
|
|
|
|
CollectStatusLogData(lifecycle, data);
|
2023-07-21 09:20:28 +02:00
|
|
|
|
fixtureLog.Log($"{GetCurrentTestName()} = {testResult} ({testDuration})");
|
2023-12-06 10:50:02 +01:00
|
|
|
|
statusLog.ConcludeTest(testResult, testDuration, data);
|
2023-04-14 14:53:39 +02:00
|
|
|
|
Stopwatch.Measure(fixtureLog, $"Teardown for {GetCurrentTestName()}", () =>
|
|
|
|
|
|
{
|
2023-09-12 10:31:55 +02:00
|
|
|
|
WriteEndTestLog(lifecycle.Log);
|
|
|
|
|
|
|
2023-09-13 15:10:19 +02:00
|
|
|
|
IncludeLogsOnTestFailure(lifecycle);
|
2023-04-14 14:53:39 +02:00
|
|
|
|
lifecycle.DeleteAllResources();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-09-12 10:31:55 +02:00
|
|
|
|
private void WriteEndTestLog(TestLog log)
|
|
|
|
|
|
{
|
|
|
|
|
|
var result = TestContext.CurrentContext.Result;
|
|
|
|
|
|
|
|
|
|
|
|
Log($"*** Finished: {GetCurrentTestName()} = {result.Outcome.Status}");
|
|
|
|
|
|
if (!string.IsNullOrEmpty(result.Message))
|
|
|
|
|
|
{
|
|
|
|
|
|
Log(result.Message);
|
|
|
|
|
|
Log($"{result.StackTrace}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-01-16 13:51:29 +01:00
|
|
|
|
private IWebCallTimeSet GetWebCallTimeSet()
|
2023-05-04 08:55:20 +02:00
|
|
|
|
{
|
2025-01-16 13:51:29 +01:00
|
|
|
|
if (ShouldUseLongTimeouts()) return new LongWebCallTimeSet();
|
|
|
|
|
|
return new DefaultWebCallTimeSet();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private IK8sTimeSet GetK8sTimeSet()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (ShouldUseLongTimeouts()) return new LongK8sTimeSet();
|
|
|
|
|
|
return new DefaultK8sTimeSet();
|
2023-05-04 08:55:20 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-06-06 15:09:52 +02:00
|
|
|
|
private bool ShouldWaitForCleanup()
|
|
|
|
|
|
{
|
|
|
|
|
|
return CurrentTestMethodHasAttribute<WaitForCleanupAttribute>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-05-04 08:55:20 +02:00
|
|
|
|
private bool ShouldUseLongTimeouts()
|
2024-04-22 11:17:47 +02:00
|
|
|
|
{
|
|
|
|
|
|
return CurrentTestMethodHasAttribute<UseLongTimeoutsAttribute>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private bool HasDontDownloadAttribute()
|
|
|
|
|
|
{
|
|
|
|
|
|
return CurrentTestMethodHasAttribute<DontDownloadLogsAttribute>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-31 09:50:02 +02:00
|
|
|
|
protected bool CurrentTestMethodHasAttribute<T>() where T : PropertyAttribute
|
|
|
|
|
|
{
|
|
|
|
|
|
return GetCurrentTestMethodAttribute<T>().Any();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected T[] GetCurrentTestMethodAttribute<T>() where T : PropertyAttribute
|
2023-05-04 08:55:20 +02:00
|
|
|
|
{
|
|
|
|
|
|
// Don't be fooled! TestContext.CurrentTest.Test allows you easy access to the attributes of the current test.
|
2024-07-31 09:50:02 +02:00
|
|
|
|
// But this doesn't work for tests making use of [TestCase] or [Combinatorial]. So instead, we use reflection here to
|
|
|
|
|
|
// fetch the attributes of type T.
|
2023-05-04 08:55:20 +02:00
|
|
|
|
var currentTest = TestContext.CurrentContext.Test;
|
|
|
|
|
|
var className = currentTest.ClassName;
|
|
|
|
|
|
var methodName = currentTest.MethodName;
|
|
|
|
|
|
|
2025-04-25 15:42:13 +02:00
|
|
|
|
var testClasses = global.TestAssemblies.SelectMany(a => a.GetTypes()).Where(c => c.FullName == className).ToArray();
|
2023-05-04 08:55:20 +02:00
|
|
|
|
var testMethods = testClasses.SelectMany(c => c.GetMethods()).Where(m => m.Name == methodName).ToArray();
|
|
|
|
|
|
|
2024-07-31 09:50:02 +02:00
|
|
|
|
return testMethods.Select(m => m.GetCustomAttribute<T>())
|
|
|
|
|
|
.Where(a => a != null)
|
|
|
|
|
|
.Cast<T>()
|
|
|
|
|
|
.ToArray();
|
2023-05-04 08:55:20 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-25 15:42:13 +02:00
|
|
|
|
protected IDownloadedLog[] DownloadAllLogs()
|
|
|
|
|
|
{
|
|
|
|
|
|
return lifecycle.DownloadAllLogs();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-09-13 15:10:19 +02:00
|
|
|
|
private void IncludeLogsOnTestFailure(TestLifecycle lifecycle)
|
2023-04-17 16:28:07 +02:00
|
|
|
|
{
|
2024-03-27 15:01:32 +01:00
|
|
|
|
var testStatus = TestContext.CurrentContext.Result.Outcome.Status;
|
|
|
|
|
|
if (ShouldDownloadAllLogs(testStatus))
|
|
|
|
|
|
{
|
|
|
|
|
|
lifecycle.Log.Log("Downloading all container logs...");
|
2025-04-25 15:42:13 +02:00
|
|
|
|
DownloadAllLogs();
|
2023-04-17 16:28:07 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-03-27 15:01:32 +01:00
|
|
|
|
private bool ShouldDownloadAllLogs(TestStatus testStatus)
|
|
|
|
|
|
{
|
2025-04-25 15:42:13 +02:00
|
|
|
|
if (global.Configuration.AlwaysDownloadContainerLogs) return true;
|
2024-04-22 11:17:47 +02:00
|
|
|
|
if (!IsDownloadingLogsEnabled()) return false;
|
2024-03-27 15:01:32 +01:00
|
|
|
|
if (testStatus == TestStatus.Failed)
|
|
|
|
|
|
{
|
2024-04-15 07:36:12 +02:00
|
|
|
|
return true;
|
2024-03-27 15:01:32 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-04-14 14:53:39 +02:00
|
|
|
|
private string GetCurrentTestName()
|
|
|
|
|
|
{
|
|
|
|
|
|
return $"[{TestContext.CurrentContext.Test.Name}]";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-25 15:42:13 +02:00
|
|
|
|
public DistTestResult GetTestResult()
|
2023-04-14 14:53:39 +02:00
|
|
|
|
{
|
2024-07-26 10:11:29 +02:00
|
|
|
|
var success = TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Passed;
|
|
|
|
|
|
var status = TestContext.CurrentContext.Result.Outcome.Status.ToString();
|
|
|
|
|
|
var result = TestContext.CurrentContext.Result.Message;
|
|
|
|
|
|
return new DistTestResult(success, status, result ?? string.Empty);
|
2023-04-14 14:53:39 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-09-13 15:10:19 +02:00
|
|
|
|
private bool IsDownloadingLogsEnabled()
|
2023-04-13 11:53:54 +02:00
|
|
|
|
{
|
2024-04-22 11:17:47 +02:00
|
|
|
|
return !HasDontDownloadAttribute();
|
2023-04-13 11:53:54 +02:00
|
|
|
|
}
|
2023-04-12 16:06:04 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-26 10:11:29 +02:00
|
|
|
|
public class DistTestResult
|
|
|
|
|
|
{
|
|
|
|
|
|
public DistTestResult(bool success, string status, string result)
|
|
|
|
|
|
{
|
|
|
|
|
|
Success = success;
|
|
|
|
|
|
Status = status;
|
|
|
|
|
|
Result = result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public bool Success { get; }
|
|
|
|
|
|
public string Status { get; }
|
|
|
|
|
|
public string Result { get; }
|
2024-08-20 15:31:45 +02:00
|
|
|
|
|
|
|
|
|
|
public override string ToString()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Success) return $"Passed ({Status}) ({Result})";
|
|
|
|
|
|
return $"Failed ({Status}) ({Result})";
|
|
|
|
|
|
}
|
2024-07-26 10:11:29 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-04-12 16:06:04 +02:00
|
|
|
|
public static class GlobalTestFailure
|
|
|
|
|
|
{
|
|
|
|
|
|
public static bool HasFailed { get; set; } = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|