From 6c556ac65a84b261d1e742af171ed9e8a76a0648 Mon Sep 17 00:00:00 2001 From: Petr Komissarov Date: Mon, 25 Mar 2024 20:55:47 +0300 Subject: [PATCH] TmsRunner refactoring --- Tms.Adapter.Core/Tms.Adapter.Core.csproj | 4 +- .../Tms.Adapter.CoreTests.csproj | 2 +- Tms.Adapter/Tms.Adapter.csproj | 2 +- TmsRunner/App.cs | 63 ++++ TmsRunner/Client/Converter.cs | 123 -------- TmsRunner/Client/ITmsClient.cs | 16 +- TmsRunner/Client/TmsClient.cs | 247 +++++++-------- .../AdapterConfig.cs | 36 +-- .../ClassConfigurationProvider.cs | 33 +- .../Configuration/ClassConfigurationSource.cs | 11 +- .../Configuration/ConfigurationExtension.cs | 7 +- .../Configuration/ConfigurationManager.cs | 155 ++++----- .../Configuration/EnvConfigurationProvider.cs | 4 +- .../Configuration/EnvConfigurationSource.cs | 6 +- TmsRunner/Enums/HttpClientNames.cs | 6 + TmsRunner/Extensions/StringExtension.cs | 32 +- TmsRunner/Handlers/DiscoveryEventHandler.cs | 42 ++- TmsRunner/Handlers/RunEventHandler.cs | 74 +++-- TmsRunner/Logger/LoggerFactory.cs | 34 -- TmsRunner/Models/AutoTest.cs | 37 ++- TmsRunner/Models/AutoTestResult.cs | 20 +- TmsRunner/Models/AutoTestStep.cs | 12 +- TmsRunner/Models/AutoTestStepResult.cs | 8 +- TmsRunner/Models/Config.cs | 28 +- TmsRunner/Models/MessageMetadata.cs | 4 +- TmsRunner/Models/MethodMetadata.cs | 12 +- TmsRunner/Models/Step.cs | 56 ++-- TmsRunner/Models/TmsSettings.cs | 14 +- TmsRunner/Program.cs | 216 +++++++------ TmsRunner/Runner.cs | 86 ----- TmsRunner/Services/FilterService.cs | 52 ++-- TmsRunner/Services/ProcessorService.cs | 294 +++++++++--------- TmsRunner/Services/Runner.cs | 53 ++++ TmsRunner/TmsRunner.csproj | 27 +- TmsRunner/Utils/Converter.cs | 120 +++++++ TmsRunner/Utils/LogParser.cs | 75 ++--- TmsRunner/Utils/Reflector.cs | 19 +- TmsRunnerTests/TmsRunnerTests.csproj | 2 +- TmsRunnerTests/Utils/LogParserTests.cs | 12 +- 39 files changed, 1009 insertions(+), 1035 deletions(-) create mode 100644 TmsRunner/App.cs delete mode 100644 TmsRunner/Client/Converter.cs rename TmsRunner/{Options => Configuration}/AdapterConfig.cs (75%) create mode 100644 TmsRunner/Enums/HttpClientNames.cs delete mode 100644 TmsRunner/Logger/LoggerFactory.cs delete mode 100644 TmsRunner/Runner.cs create mode 100644 TmsRunner/Services/Runner.cs create mode 100644 TmsRunner/Utils/Converter.cs diff --git a/Tms.Adapter.Core/Tms.Adapter.Core.csproj b/Tms.Adapter.Core/Tms.Adapter.Core.csproj index 83d713d..7d036c8 100644 --- a/Tms.Adapter.Core/Tms.Adapter.Core.csproj +++ b/Tms.Adapter.Core/Tms.Adapter.Core.csproj @@ -29,10 +29,10 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + diff --git a/Tms.Adapter.CoreTests/Tms.Adapter.CoreTests.csproj b/Tms.Adapter.CoreTests/Tms.Adapter.CoreTests.csproj index 6aea926..346ce48 100644 --- a/Tms.Adapter.CoreTests/Tms.Adapter.CoreTests.csproj +++ b/Tms.Adapter.CoreTests/Tms.Adapter.CoreTests.csproj @@ -11,7 +11,7 @@ - + diff --git a/Tms.Adapter/Tms.Adapter.csproj b/Tms.Adapter/Tms.Adapter.csproj index c6c7f9a..532b3d3 100644 --- a/Tms.Adapter/Tms.Adapter.csproj +++ b/Tms.Adapter/Tms.Adapter.csproj @@ -32,7 +32,7 @@ - + diff --git a/TmsRunner/App.cs b/TmsRunner/App.cs new file mode 100644 index 0000000..a3c355c --- /dev/null +++ b/TmsRunner/App.cs @@ -0,0 +1,63 @@ +using Microsoft.Extensions.Logging; +using TmsRunner.Client; +using TmsRunner.Configuration; +using TmsRunner.Models; +using TmsRunner.Services; + +namespace TmsRunner; + +public class App(ILogger logger, AdapterConfig adapterConfig, TmsSettings tmsSettings, ITmsClient tmsClient, FilterService filterService, Runner runner) +{ + public async Task RunAsync() + { + logger.LogInformation("Adapter works in {Mode} mode", tmsSettings.AdapterMode); + logger.LogDebug("Parameters:"); + logger.LogDebug("Runner Path: {Path}", adapterConfig.RunnerPath); + logger.LogDebug("Test Assembly Path: {Path}", adapterConfig.TestAssemblyPath); + logger.LogDebug("Test Adapter Path: {Path}", adapterConfig.TestAdapterPath); + logger.LogDebug("Test Logger Path: {Path}", adapterConfig.LoggerPath); + + runner.InitialiseRunner(); + var testCases = runner.DiscoverTests(); + logger.LogInformation("Discovered Tests Count: {Count}", testCases.Count); + + if (testCases.Count == 0) + { + logger.LogInformation("Can not found tests for run"); + + return 1; + } + + switch (tmsSettings.AdapterMode) + { + case 0: + { + var testCaseForRun = await tmsClient.GetAutoTestsForRunAsync(tmsSettings.TestRunId).ConfigureAwait(false); + testCases = filterService.FilterTestCases(adapterConfig.TestAssemblyPath, testCaseForRun, testCases); + + break; + } + case 2: + { + tmsSettings.TestRunId = await tmsClient.CreateTestRunAsync().ConfigureAwait(false); + + if (!string.IsNullOrEmpty(adapterConfig.TmsLabelsOfTestsToRun)) + { + testCases = filterService.FilterTestCasesByLabels(adapterConfig, testCases); + } + + break; + } + } + + logger.LogInformation("Running tests: {Count}", testCases.Count); + await runner.RunSelectedTestsAsync(testCases).ConfigureAwait(false); + + if (tmsSettings.AdapterMode == 2) + { + logger.LogInformation("Test run {TestRunId} finished.", tmsSettings.TestRunId); + } + + return 0; + } +} diff --git a/TmsRunner/Client/Converter.cs b/TmsRunner/Client/Converter.cs deleted file mode 100644 index e3d84b9..0000000 --- a/TmsRunner/Client/Converter.cs +++ /dev/null @@ -1,123 +0,0 @@ -using TestIT.ApiClient.Model; -using TmsRunner.Models; - -namespace TmsRunner.Client; - -public class Converter -{ - public static CreateAutoTestRequest ConvertAutoTestDtoToPostModel(AutoTest dto, string projectId) - { - var links = dto.Links?.Select(l => - new LinkPostModel( - l.Title, - l.Url, - l.Description, - Enum.Parse(l.Type.ToString()!)) - ).ToList(); - - return new CreateAutoTestRequest(externalId: dto.ExternalId, name: dto.Name) - { - ExternalId = dto.ExternalId, - Links = links!, - ProjectId = new Guid(projectId), - Namespace = dto.Namespace, - Classname = dto.Classname, - Steps = ConvertStepsToModel(dto.Steps), - Setup = ConvertStepsToModel(dto.Setup), - Teardown = ConvertStepsToModel(dto.Teardown), - Title = dto.Title, - Description = dto.Description, - Labels = ConvertLabelsToModel(dto.Labels) - }; - } - - public static UpdateAutoTestRequest ConvertAutoTestDtoToPutModel(AutoTest dto, string projectId) - { - var links = dto.Links.Select(l => - new LinkPutModel( - title: l.Title, - url: l.Url, - description: l.Description, - type: Enum.Parse(l.Type.ToString()!)) - ).ToList(); - - - return new UpdateAutoTestRequest(externalId: dto.ExternalId, name: dto.Name) - { - Links = links, - ProjectId = new Guid(projectId), Name = dto.Name, - Namespace = dto.Namespace, - Classname = dto.Classname, - Steps = ConvertStepsToModel(dto.Steps), - Setup = ConvertStepsToModel(dto.Setup), - Teardown = ConvertStepsToModel(dto.Teardown), - Title = dto.Title, - Description = dto.Description, - Labels = ConvertLabelsToModel(dto.Labels), - IsFlaky = dto.IsFlaky - }; - } - - public static AutoTestResultsForTestRunModel ConvertResultToModel(AutoTestResult dto, string configurationId) - { - var links = dto.Links?.Select(l => - new LinkPostModel( - l.Title, - l.Url, - l.Description, - Enum.Parse(l.Type.ToString()!)) - ).ToList(); - - return new AutoTestResultsForTestRunModel( - autoTestExternalId: dto.ExternalId, - outcome: Enum.Parse(dto.Outcome.ToString())) - { - ConfigurationId = new Guid(configurationId), - Links = links, - Message = dto.Message, - Traces = dto.Traces, - StartedOn = dto.StartedOn, - CompletedOn = dto.CompletedOn, - Duration = dto.Duration, - Attachments = dto.Attachments.Select(a => new AttachmentPutModel(a)).ToList(), - Parameters = dto.Parameters, - StepResults = ConvertResultStepToModel(dto.StepResults), - SetupResults = ConvertResultStepToModel(dto.SetupResults), - TeardownResults = ConvertResultStepToModel(dto.TeardownResults) - }; - } - - private static List? ConvertLabelsToModel(IEnumerable? labels) - { - return labels?.Select(l => - new LabelPostModel(l)) - .ToList(); - } - - private static List ConvertResultStepToModel( - IEnumerable dtos) - { - return dtos - .Select(s => new AttachmentPutModelAutoTestStepResultsModel - { - Title = s.Title, - Description = s.Description, - StartedOn = s.StartedOn, - CompletedOn = s.CompletedOn, - Duration = s.Duration, - Attachments = s.Attachments.Select(a => new AttachmentPutModel(a)).ToList(), - Parameters = s.Parameters, - StepResults = ConvertResultStepToModel(s.Steps), - Outcome = Enum.Parse(s.Outcome) - }).ToList(); - } - - private static List ConvertStepsToModel(IEnumerable stepDtos) - { - return stepDtos - .Select(s => new AutoTestStepModel( - s.Title, - s.Description, - ConvertStepsToModel(s.Steps))).ToList(); - } -} \ No newline at end of file diff --git a/TmsRunner/Client/ITmsClient.cs b/TmsRunner/Client/ITmsClient.cs index cb055b2..725c072 100644 --- a/TmsRunner/Client/ITmsClient.cs +++ b/TmsRunner/Client/ITmsClient.cs @@ -5,12 +5,12 @@ namespace TmsRunner.Client; public interface ITmsClient { - Task CreateTestRun(); - Task> GetAutoTestsForRun(string testRunId); - Task SubmitResultToTestRun(string guid, AutoTestResult result); - Task UploadAttachment(string fileName, Stream content); - Task GetAutotestByExternalId(string externalId); - Task CreateAutotest(AutoTest model); - Task UpdateAutotest(AutoTest model); - Task TryLinkAutoTestToWorkItem(string autotestId, IEnumerable workItemIds); + Task CreateTestRunAsync(); + Task> GetAutoTestsForRunAsync(string? testRunId); + Task SubmitResultToTestRunAsync(string? guid, AutoTestResult result); + Task UploadAttachmentAsync(string fileName, Stream content); + Task GetAutotestByExternalIdAsync(string? externalId); + Task CreateAutotestAsync(AutoTest model); + Task UpdateAutotestAsync(AutoTest model); + Task TryLinkAutoTestToWorkItemAsync(string autotestId, IEnumerable workItemIds); } \ No newline at end of file diff --git a/TmsRunner/Client/TmsClient.cs b/TmsRunner/Client/TmsClient.cs index 4c4a347..1ea017e 100644 --- a/TmsRunner/Client/TmsClient.cs +++ b/TmsRunner/Client/TmsClient.cs @@ -1,180 +1,149 @@ -using Serilog; +using Microsoft.Extensions.Logging; using TestIT.ApiClient.Api; using TestIT.ApiClient.Client; using TestIT.ApiClient.Model; -using TmsRunner.Logger; using TmsRunner.Models; +using TmsRunner.Utils; -namespace TmsRunner.Client +namespace TmsRunner.Client; + +public sealed class TmsClient(ILogger logger, TmsSettings settings, ITestRunsApiAsync testRunsApi, IAttachmentsApiAsync attachmentsApi, IAutoTestsApiAsync autoTestsApi) : ITmsClient { - public class TmsClient : ITmsClient + public async Task CreateTestRunAsync() { - private readonly TmsSettings _settings; - private readonly ILogger _logger; - private readonly TestRunsApi _testRuns; - private readonly AttachmentsApi _attachments; - private readonly AutoTestsApi _autoTests; - - public TmsClient(TmsSettings settings) + var createTestRunRequestBody = new CreateEmptyRequest { - _logger = LoggerFactory.GetLogger().ForContext(); - _settings = settings; - - var cfg = new TestIT.ApiClient.Client.Configuration { BasePath = settings.Url }; - cfg.AddApiKeyPrefix("Authorization", "PrivateToken"); - cfg.AddApiKey("Authorization", settings.PrivateToken); + ProjectId = new Guid(settings.ProjectId ?? string.Empty), + Name = (string.IsNullOrEmpty(settings.TestRunName) ? null : settings.TestRunName)! + }; - var httpClientHandler = new HttpClientHandler(); - httpClientHandler.ServerCertificateCustomValidationCallback = (_, _, _, _) => _settings.CertValidation; + logger.LogDebug("Creating test run {@TestRun}", createTestRunRequestBody); - _testRuns = new TestRunsApi(new HttpClient(), cfg, httpClientHandler); - _attachments = new AttachmentsApi(new HttpClient(), cfg, httpClientHandler); - _autoTests = new AutoTestsApi(new HttpClient(), cfg, httpClientHandler); - } + var testRun = await testRunsApi.CreateEmptyAsync(createTestRunRequestBody).ConfigureAwait(false) ?? throw new Exception($"Could not find project with id: {settings.ProjectId}"); + logger.LogDebug("Created test run {@TestRun}", testRun); - public async Task CreateTestRun() - { - var createTestRunRequestBody = new CreateEmptyRequest - { - ProjectId = new Guid(_settings.ProjectId), - Name = (string.IsNullOrEmpty(_settings.TestRunName) ? null : _settings.TestRunName)! - }; - - _logger.Debug("Creating test run {@TestRun}", createTestRunRequestBody); + return testRun.Id.ToString(); + } - var testRun = await _testRuns.CreateEmptyAsync(createTestRunRequestBody); + public async Task> GetAutoTestsForRunAsync(string? testRunId) + { + logger.LogDebug("Getting autotests for run from test run {Id}", testRunId); - if (testRun is null) - { - throw new Exception($"Could not find project with id: {_settings.ProjectId}"); - } + var testRun = await testRunsApi.GetTestRunByIdAsync(new Guid(testRunId ?? string.Empty)).ConfigureAwait(false); - _logger.Debug("Created test run {@TestRun}", testRun); + var autotests = testRun.TestResults.Where(x => !x.AutoTest.IsDeleted).Select(x => x.AutoTest.ExternalId).ToList(); - return testRun.Id.ToString(); - } + logger.LogDebug( + "Autotests for run from test run {Id}: {@Autotests}", + testRunId, + autotests); - public async Task> GetAutoTestsForRun(string testRunId) - { - _logger.Debug("Getting autotests for run from test run {Id}", testRunId); + return autotests as List; + } - var testRun = await _testRuns.GetTestRunByIdAsync(new Guid(testRunId)); + public async Task SubmitResultToTestRunAsync(string? id, AutoTestResult result) + { + logger.LogDebug("Submitting test result {@Result} to test run {@Id}", result, id); - var autotests = testRun.TestResults.Where(x => !x.AutoTest.IsDeleted).Select(x => x.AutoTest.ExternalId).ToList(); + var model = Converter.ConvertResultToModel(result, settings.ConfigurationId); + _ = await testRunsApi.SetAutoTestResultsForTestRunAsync(new Guid(id ?? string.Empty), [model]).ConfigureAwait(false); - _logger.Debug( - "Autotests for run from test run {Id}: {@Autotests}", - testRunId, - autotests); + logger.LogDebug("Submit test result to test run {Id} is successfully", id); + } - return autotests; - } + public async Task UploadAttachmentAsync(string fileName, Stream content) + { + logger.LogDebug("Uploading attachment {Name}", fileName); - public async Task SubmitResultToTestRun(string id, AutoTestResult result) - { - _logger.Debug("Submitting test result {@Result} to test run {@Id}", result, id); + var response = await attachmentsApi.ApiV2AttachmentsPostAsync( + new FileParameter( + filename: Path.GetFileName(fileName), + content: content, + contentType: MimeTypes.GetMimeType(fileName)) + ).ConfigureAwait(false); - var model = Converter.ConvertResultToModel(result, _settings.ConfigurationId); - await _testRuns.SetAutoTestResultsForTestRunAsync(new Guid(id), - new List { model }); + logger.LogDebug("Upload attachment {@Attachment} is successfully", response); - _logger.Debug("Submit test result to test run {Id} is successfully", id); - } + return response; + } - public async Task UploadAttachment(string fileName, Stream content) - { - _logger.Debug("Uploading attachment {Name}", fileName); + public async Task GetAutotestByExternalIdAsync(string? externalId) + { + logger.LogDebug("Getting autotest by external id {Id}", externalId); - var response = await _attachments.ApiV2AttachmentsPostAsync( - new FileParameter( - filename: Path.GetFileName(fileName), - content: content, - contentType: MimeTypes.GetMimeType(fileName)) - ); + var filter = new ApiV2AutoTestsSearchPostRequest( + filter: new AutotestsSelectModelFilter + { + ExternalIds = [externalId ?? string.Empty], + ProjectIds = settings.ProjectId == null ? [] : [new Guid(settings.ProjectId)], + IsDeleted = false + }, + includes: new AutotestsSelectModelIncludes() + ); + + var autotests = await autoTestsApi.ApiV2AutoTestsSearchPostAsync(apiV2AutoTestsSearchPostRequest: filter).ConfigureAwait(false); + var autotest = autotests.FirstOrDefault(); + + logger.LogDebug( + "Get autotest {@Autotest} by external id {Id}", + autotest, + externalId); + + return autotest; + } - _logger.Debug("Upload attachment {@Attachment} is successfully", response); + public async Task CreateAutotestAsync(AutoTest dto) + { + logger.LogDebug("Creating autotest {@Autotest}", dto); - return response; - } + var model = Converter.ConvertAutoTestDtoToPostModel(dto, settings.ProjectId); + model.ShouldCreateWorkItem = settings.AutomaticCreationTestCases; + var response = await autoTestsApi.CreateAutoTestAsync(model).ConfigureAwait(false); - public async Task GetAutotestByExternalId(string externalId) - { - _logger.Debug("Getting autotest by external id {Id}", externalId); - - var filter = new ApiV2AutoTestsSearchPostRequest( - filter: new AutotestsSelectModelFilter - { - ExternalIds = new List { externalId }, - ProjectIds = new List { new Guid(_settings.ProjectId) }, - IsDeleted = false - }, - includes: new AutotestsSelectModelIncludes() - ); - - var autotests = await _autoTests.ApiV2AutoTestsSearchPostAsync(apiV2AutoTestsSearchPostRequest: filter); - var autotest = autotests.FirstOrDefault(); - - _logger.Debug( - "Get autotest {@Autotest} by external id {Id}", - autotest, - externalId); - - return autotest; - } + logger.LogDebug("Create autotest {@Autotest} is successfully", response); - public async Task CreateAutotest(AutoTest dto) - { - _logger.Debug("Creating autotest {@Autotest}", dto); + return response; + } - var model = Converter.ConvertAutoTestDtoToPostModel(dto, _settings.ProjectId); - model.ShouldCreateWorkItem = _settings.AutomaticCreationTestCases; - var response = await _autoTests.CreateAutoTestAsync(model); + public async Task UpdateAutotestAsync(AutoTest dto) + { + logger.LogDebug("Updating autotest {@Autotest}", dto); - _logger.Debug("Create autotest {@Autotest} is successfully", response); + var model = Converter.ConvertAutoTestDtoToPutModel(dto, settings.ProjectId); + await autoTestsApi.UpdateAutoTestAsync(model).ConfigureAwait(false); - return response; - } + logger.LogDebug("Update autotest {@Autotest} is successfully", model); + } - public async Task UpdateAutotest(AutoTest dto) + public async Task TryLinkAutoTestToWorkItemAsync(string autotestId, IEnumerable workItemIds) + { + foreach (var workItemId in workItemIds) { - _logger.Debug("Updating autotest {@Autotest}", dto); - - var model = Converter.ConvertAutoTestDtoToPutModel(dto, _settings.ProjectId); - await _autoTests.UpdateAutoTestAsync(model); - - _logger.Debug("Update autotest {@Autotest} is successfully", model); - } + logger.LogDebug( + "Linking autotest {AutotestId} to workitem {WorkitemId}", + autotestId, + workItemId); - public async Task TryLinkAutoTestToWorkItem(string autotestId, IEnumerable workItemIds) - { - foreach (var workItemId in workItemIds) + try { - _logger.Debug( - "Linking autotest {AutotestId} to workitem {WorkitemId}", - autotestId, - workItemId); - - try - { - await _autoTests.LinkAutoTestToWorkItemAsync(autotestId, new LinkAutoTestToWorkItemRequest(workItemId)); - } - catch (ApiException e) when (e.Message.Contains("does not exist")) - { - _logger.Error( - "Cannot link autotest {AutotestId} to work item {WorkItemId}: work item does not exist", - autotestId, - workItemId); - - return false; - } - - _logger.Debug( - "Link autotest {AutotestId} to workitem {WorkitemId} is successfully", - autotestId, - workItemId); + await autoTestsApi.LinkAutoTestToWorkItemAsync(autotestId, new LinkAutoTestToWorkItemRequest(workItemId ?? string.Empty)).ConfigureAwait(false); } + catch (ApiException e) when (e.Message.Contains("does not exist")) + { + logger.LogError( + "Cannot link autotest {AutotestId} to work item {WorkItemId}: work item does not exist", + autotestId, + workItemId); - return true; + return false; + } + + logger.LogDebug( + "Link autotest {AutotestId} to workitem {WorkitemId} is successfully", + autotestId, + workItemId); } + + return true; } } \ No newline at end of file diff --git a/TmsRunner/Options/AdapterConfig.cs b/TmsRunner/Configuration/AdapterConfig.cs similarity index 75% rename from TmsRunner/Options/AdapterConfig.cs rename to TmsRunner/Configuration/AdapterConfig.cs index 7a82a54..75af0f6 100644 --- a/TmsRunner/Options/AdapterConfig.cs +++ b/TmsRunner/Configuration/AdapterConfig.cs @@ -1,66 +1,66 @@ using CommandLine; using TmsRunner.Models; -namespace TmsRunner.Options; +namespace TmsRunner.Configuration; -public class AdapterConfig +public sealed class AdapterConfig { [Option('r', "runner", Required = true, HelpText = "Set path to test runner. Example: --runner '/opt/homebrew/Cellar/dotnet/6.0.110/libexec/sdk/6.0.110/vstest.console.dll'")] - public string RunnerPath { get; set; } + public string? RunnerPath { get; set; } [Option('t', "testassembly", Required = true, HelpText = "Set path to test assembly. Example: --testassembly '/Tests/tests.dll'")] - public string TestAssemblyPath { get; set; } + public string? TestAssemblyPath { get; set; } [Option('a', "testadapter", Required = false, HelpText = "Set path to test adapter. Example: --testadapter '/Tests/testsAdapter.dll'")] - public string TestAdapterPath { get; set; } + public string? TestAdapterPath { get; set; } [Option('l', "logger", Required = false, HelpText = "Set path to logger. Example: --logger '/Tests/logger.dll'")] - public string LoggerPath { get; set; } + public string? LoggerPath { get; set; } [Option("tmsLabelsOfTestsToRun", Required = false, HelpText = "Set labels of autotests to run. Example: --tmsLabelsOfTestsToRun smoke OR --tmsLabelsOfTestsToRun smoke,prod,cloud")] - public string TmsLabelsOfTestsToRun { get; set; } + public string? TmsLabelsOfTestsToRun { get; set; } [Option('d', "debug", Required = false, HelpText = "Set debug level for logging. Example: --debug")] public bool IsDebug { get; set; } [Option("tmsUrl", Required = false, HelpText = "Set TMS host address.")] - public string TmsUrl { get; set; } + public string? TmsUrl { get; set; } [Option("tmsPrivateToken", Required = false, HelpText = "Set private token.")] - public string TmsPrivateToken { get; set; } + public string? TmsPrivateToken { get; set; } [Option("tmsProjectId", Required = false, HelpText = "Set project id.")] - public string TmsProjectId { get; set; } + public string? TmsProjectId { get; set; } [Option("tmsConfigurationId", Required = false, HelpText = "Set configuration id.")] - public string TmsConfigurationId { get; set; } + public string? TmsConfigurationId { get; set; } [Option("tmsTestRunId", Required = false, HelpText = "Set test run id.")] - public string TmsTestRunId { get; set; } + public string? TmsTestRunId { get; set; } [Option("tmsTestRunName", Required = false, HelpText = "Set test run name.")] - public string TmsTestRunName { get; set; } + public string? TmsTestRunName { get; set; } [Option("tmsAdapterMode", Required = false, HelpText = "Set adapter mode.")] - public string TmsAdapterMode { get; set; } + public string? TmsAdapterMode { get; set; } [Option("tmsConfigFile", Required = false, HelpText = "Set configuration file name.")] - public string TmsConfigFile { get; set; } + public string? TmsConfigFile { get; set; } [Option("tmsRunSettings", Required = false, HelpText = "Set run settings.")] - public string TmsRunSettings { get; set; } + public string? TmsRunSettings { get; set; } [Option("tmsAutomaticCreationTestCases", Required = false, HelpText = "Set automatic creation test cases.")] - public string TmsAutomaticCreationTestCases { get; set; } + public string? TmsAutomaticCreationTestCases { get; set; } [Option("tmsCertValidation", Default = "true", Required = false, HelpText = "Set certificate validation.")] - public string TmsCertValidation { get; set; } + public string? TmsCertValidation { get; set; } public Config ToInternalConfig() { diff --git a/TmsRunner/Configuration/ClassConfigurationProvider.cs b/TmsRunner/Configuration/ClassConfigurationProvider.cs index bf29927..3740564 100644 --- a/TmsRunner/Configuration/ClassConfigurationProvider.cs +++ b/TmsRunner/Configuration/ClassConfigurationProvider.cs @@ -3,30 +3,23 @@ namespace TmsRunner.Configuration; -public class ClassConfigurationProvider : ConfigurationProvider +public sealed class ClassConfigurationProvider(Config config) : ConfigurationProvider { - private readonly Config _config; - - public ClassConfigurationProvider(Config config) - { - _config = config; - } - public override void Load() { - var data = new Dictionary + var data = new Dictionary { - { "Url", _config.TmsUrl }, - { "PrivateToken", _config.TmsPrivateToken }, - { "ProjectId", _config.TmsProjectId }, - { "ConfigurationId", _config.TmsConfigurationId }, - { "TestRunId", _config.TmsTestRunId }, - { "TestRunName", _config.TmsTestRunName }, - { "AdapterMode", _config.TmsAdapterMode }, - { "ConfigFile", _config.TmsConfigFile }, - { "RunSettings", _config.TmsRunSettings }, - { "AutomaticCreationTestCases", _config.TmsAutomaticCreationTestCases }, - { "CertValidation", _config.TmsCertValidation } + { "Url", config.TmsUrl }, + { "PrivateToken", config.TmsPrivateToken }, + { "ProjectId", config.TmsProjectId }, + { "ConfigurationId", config.TmsConfigurationId }, + { "TestRunId", config.TmsTestRunId }, + { "TestRunName", config.TmsTestRunName }, + { "AdapterMode", config.TmsAdapterMode }, + { "ConfigFile", config.TmsConfigFile }, + { "RunSettings", config.TmsRunSettings }, + { "AutomaticCreationTestCases", config.TmsAutomaticCreationTestCases }, + { "CertValidation", config.TmsCertValidation } }; Data = data diff --git a/TmsRunner/Configuration/ClassConfigurationSource.cs b/TmsRunner/Configuration/ClassConfigurationSource.cs index f6e7e61..e8c903c 100644 --- a/TmsRunner/Configuration/ClassConfigurationSource.cs +++ b/TmsRunner/Configuration/ClassConfigurationSource.cs @@ -3,15 +3,12 @@ namespace TmsRunner.Configuration; -public class ClassConfigurationSource : IConfigurationSource +public sealed class ClassConfigurationSource(Config config) : IConfigurationSource { - private readonly Config _config; + private readonly Config _config = config; - public ClassConfigurationSource(Config config) + public IConfigurationProvider Build(IConfigurationBuilder builder) { - _config = config; + return new ClassConfigurationProvider(_config); } - - public IConfigurationProvider Build(IConfigurationBuilder builder) - => new ClassConfigurationProvider(_config); } \ No newline at end of file diff --git a/TmsRunner/Configuration/ConfigurationExtension.cs b/TmsRunner/Configuration/ConfigurationExtension.cs index 8c05784..144767b 100644 --- a/TmsRunner/Configuration/ConfigurationExtension.cs +++ b/TmsRunner/Configuration/ConfigurationExtension.cs @@ -5,10 +5,9 @@ namespace TmsRunner.Configuration; public static class ConfigurationExtension { - public static void AddCustomConfiguration(this IConfigurationBuilder builder, - Config config) + public static void AddCustomConfiguration(this IConfigurationBuilder builder, Config config) { - builder.Add(new EnvConfigurationSource()); - builder.Add(new ClassConfigurationSource(config)); + _ = builder.Add(new EnvConfigurationSource()); + _ = builder.Add(new ClassConfigurationSource(config)); } } \ No newline at end of file diff --git a/TmsRunner/Configuration/ConfigurationManager.cs b/TmsRunner/Configuration/ConfigurationManager.cs index 71e341b..d1e60d6 100644 --- a/TmsRunner/Configuration/ConfigurationManager.cs +++ b/TmsRunner/Configuration/ConfigurationManager.cs @@ -1,80 +1,81 @@ -using System.Configuration; -using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration; +using System.Configuration; using TmsRunner.Extensions; using TmsRunner.Models; -namespace TmsRunner.Configuration +namespace TmsRunner.Configuration; + +public static class ConfigurationManager { - public static class ConfigurationManager + private const string EnvConfigFile = "TMS_CONFIG_FILE"; + private const string DefaultConfigFileName = "Tms.config.json"; + + public static TmsSettings Configure(Config adapterConfig, string pathToConfFile) { - private const string EnvConfigFile = "TMS_CONFIG_FILE"; - private const string DefaultConfigFileName = "Tms.config.json"; + if (string.IsNullOrWhiteSpace(pathToConfFile)) + { + throw new ArgumentException("The path of config directory is empty", nameof(pathToConfFile)); + } - public static TmsSettings Configure(Config adapterConfig, string pathToConfFile) + var configFileName = GetConfigFileName(adapterConfig.TmsConfigFile); + var configurationFileLocation = Path.Combine(pathToConfFile, configFileName); + + var configBuilder = new ConfigurationBuilder(); + + if (File.Exists(configurationFileLocation)) { - if (string.IsNullOrWhiteSpace(pathToConfFile)) - { - throw new ArgumentException("The path of config directory is empty", nameof(pathToConfFile)); - } + _ = configBuilder.AddJsonFile(configurationFileLocation); + } + else + { + Console.WriteLine($"Configuration file was not found at {configurationFileLocation}"); + } - var configFileName = GetConfigFileName(adapterConfig.TmsConfigFile); - var configurationFileLocation = Path.Combine(pathToConfFile, configFileName); + configBuilder.AddCustomConfiguration(adapterConfig); + var config = configBuilder.Build(); + var tmsSettings = new TmsSettings(); + config.Bind(tmsSettings); - var configBuilder = new ConfigurationBuilder(); + Validate(tmsSettings); - if (File.Exists(configurationFileLocation)) - { - configBuilder.AddJsonFile(configurationFileLocation); - } - else - { - Console.WriteLine($"Configuration file was not found at {configurationFileLocation}"); - } + return tmsSettings; + } - configBuilder.AddCustomConfiguration(adapterConfig); - var config = configBuilder.Build(); - var tmsSettings = new TmsSettings(); - config.Bind(tmsSettings); + private static string GetConfigFileName(string? path) + { + var defaultConfFileName = DefaultConfigFileName; + var envConfFileName = Environment.GetEnvironmentVariable(EnvConfigFile); + defaultConfFileName = defaultConfFileName.AssignIfNullOrEmpty(envConfFileName); - Validate(tmsSettings); + return defaultConfFileName.AssignIfNullOrEmpty(path); + } - return tmsSettings; + private static void Validate(TmsSettings settings) + { + if (string.IsNullOrWhiteSpace(settings.Url)) + { + throw new ConfigurationErrorsException("Url is empty"); } - private static string GetConfigFileName(string path) + if (string.IsNullOrWhiteSpace(settings.PrivateToken)) { - var defaultConfFileName = DefaultConfigFileName; - var envConfFileName = Environment.GetEnvironmentVariable(EnvConfigFile); - defaultConfFileName = defaultConfFileName.AssignIfNullOrEmpty(envConfFileName); - return defaultConfFileName.AssignIfNullOrEmpty(path); + throw new ConfigurationErrorsException("Private token is empty"); } - private static void Validate(TmsSettings settings) + if (string.IsNullOrWhiteSpace(settings.ConfigurationId)) { - if (string.IsNullOrWhiteSpace(settings.Url)) - { - throw new ConfigurationErrorsException("Url is empty"); - } - - if (string.IsNullOrWhiteSpace(settings.PrivateToken)) - { - throw new ConfigurationErrorsException("Private token is empty"); - } - - if (string.IsNullOrWhiteSpace(settings.ConfigurationId)) - { - throw new ConfigurationErrorsException("Configuration id is empty"); - } + throw new ConfigurationErrorsException("Configuration id is empty"); + } - if (!string.IsNullOrWhiteSpace(settings.RunSettings) && !IsValidXml(settings.RunSettings)) - { - throw new ConfigurationErrorsException("Run settings is invalid"); - } + if (!string.IsNullOrWhiteSpace(settings.RunSettings) && !IsValidXml(settings.RunSettings)) + { + throw new ConfigurationErrorsException("Run settings is invalid"); + } - switch (settings.AdapterMode) - { - case 0: - case 1: + switch (settings.AdapterMode) + { + case 0: + case 1: { if (string.IsNullOrWhiteSpace(settings.TestRunId)) { @@ -84,33 +85,35 @@ private static void Validate(TmsSettings settings) break; } - case 2: - if (string.IsNullOrWhiteSpace(settings.ProjectId) || !string.IsNullOrWhiteSpace(settings.TestRunId)) - { - throw new ConfigurationErrorsException( - "Adapter works in mode 2. Config should contains project id and configuration id. Also doesn't contains test run id."); - } + case 2: + if (string.IsNullOrWhiteSpace(settings.ProjectId) || !string.IsNullOrWhiteSpace(settings.TestRunId)) + { + throw new ConfigurationErrorsException( + "Adapter works in mode 2. Config should contains project id and configuration id. Also doesn't contains test run id."); + } - break; - default: - throw new Exception($"Incorrect adapter mode: {settings.AdapterMode}"); - } + break; + default: + throw new Exception($"Incorrect adapter mode: {settings.AdapterMode}"); } + } - private static bool IsValidXml(string xmlStr) + private static bool IsValidXml(string xmlStr) + { + try { - try - { - if (string.IsNullOrEmpty(xmlStr)) return false; - - var xmlDoc = new System.Xml.XmlDocument(); - xmlDoc.LoadXml(xmlStr); - return true; - } - catch (System.Xml.XmlException) + if (string.IsNullOrEmpty(xmlStr)) { return false; } + + var xmlDoc = new System.Xml.XmlDocument(); + xmlDoc.LoadXml(xmlStr); + return true; + } + catch (System.Xml.XmlException) + { + return false; } } } \ No newline at end of file diff --git a/TmsRunner/Configuration/EnvConfigurationProvider.cs b/TmsRunner/Configuration/EnvConfigurationProvider.cs index 2b00d9b..cec8b9d 100644 --- a/TmsRunner/Configuration/EnvConfigurationProvider.cs +++ b/TmsRunner/Configuration/EnvConfigurationProvider.cs @@ -2,7 +2,7 @@ namespace TmsRunner.Configuration; -public class EnvConfigurationProvider : ConfigurationProvider +public sealed class EnvConfigurationProvider : ConfigurationProvider { private const string EnvTmsUrl = "TMS_URL"; private const string EnvTmsPrivateToken = "TMS_PRIVATE_TOKEN"; @@ -17,7 +17,7 @@ public class EnvConfigurationProvider : ConfigurationProvider public override void Load() { - var data = new Dictionary + var data = new Dictionary { { "Url", Environment.GetEnvironmentVariable(EnvTmsUrl) }, { "PrivateToken", Environment.GetEnvironmentVariable(EnvTmsPrivateToken) }, diff --git a/TmsRunner/Configuration/EnvConfigurationSource.cs b/TmsRunner/Configuration/EnvConfigurationSource.cs index eaf98b9..d9b0fa6 100644 --- a/TmsRunner/Configuration/EnvConfigurationSource.cs +++ b/TmsRunner/Configuration/EnvConfigurationSource.cs @@ -2,8 +2,10 @@ namespace TmsRunner.Configuration; -public class EnvConfigurationSource : IConfigurationSource +public sealed class EnvConfigurationSource : IConfigurationSource { public IConfigurationProvider Build(IConfigurationBuilder builder) - => new EnvConfigurationProvider(); + { + return new EnvConfigurationProvider(); + } } \ No newline at end of file diff --git a/TmsRunner/Enums/HttpClientNames.cs b/TmsRunner/Enums/HttpClientNames.cs new file mode 100644 index 0000000..ce210d5 --- /dev/null +++ b/TmsRunner/Enums/HttpClientNames.cs @@ -0,0 +1,6 @@ +namespace TmsRunner.Enums; + +public enum HttpClientNames +{ + Default, +} diff --git a/TmsRunner/Extensions/StringExtension.cs b/TmsRunner/Extensions/StringExtension.cs index 6bae3fc..9500089 100644 --- a/TmsRunner/Extensions/StringExtension.cs +++ b/TmsRunner/Extensions/StringExtension.cs @@ -1,26 +1,24 @@ using System.Security.Cryptography; using System.Text; -namespace TmsRunner.Extensions +namespace TmsRunner.Extensions; + +public static class StringExtension { - public static class StringExtension + public static string AssignIfNullOrEmpty(this string str, string? value) + { + return string.IsNullOrWhiteSpace(value) ? str : value; + } + + public static string ComputeHash(this string str) { - public static string AssignIfNullOrEmpty(this string str, string? value) - { - return string.IsNullOrWhiteSpace(value) ? str : value; - } + var hash = SHA256.HashData(Encoding.UTF8.GetBytes(str)); - public static string ComputeHash(this string str) - { - var sha = SHA256.Create(); - var hash = sha.ComputeHash(Encoding.UTF8.GetBytes(str)); - - return BitConverter.ToUInt32(hash).ToString(); - } + return BitConverter.ToUInt32(hash).ToString(); + } - public static string RemoveQuotes(this string str) - { - return str.Replace("'", "").Replace("\"", ""); - } + public static string RemoveQuotes(this string str) + { + return str.Replace("'", "").Replace("\"", ""); } } \ No newline at end of file diff --git a/TmsRunner/Handlers/DiscoveryEventHandler.cs b/TmsRunner/Handlers/DiscoveryEventHandler.cs index b7336d0..2865511 100644 --- a/TmsRunner/Handlers/DiscoveryEventHandler.cs +++ b/TmsRunner/Handlers/DiscoveryEventHandler.cs @@ -1,35 +1,26 @@ +using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; -using Serilog; -using TmsRunner.Logger; namespace TmsRunner.Handlers; -public class DiscoveryEventHandler : ITestDiscoveryEventsHandler +public sealed class DiscoveryEventHandler(ILogger logger, AutoResetEvent waitHandle) : ITestDiscoveryEventsHandler { - private AutoResetEvent waitHandle; - private readonly ILogger _logger; - public List DiscoveredTestCases { get; } + public List DiscoveredTestCases { get; } = []; - public DiscoveryEventHandler(AutoResetEvent waitHandle) + public void HandleDiscoveredTests(IEnumerable? discoveredTestCases) { - this.waitHandle = waitHandle; - DiscoveredTestCases = new List(); - _logger = LoggerFactory.GetLogger().ForContext(); - } - - public void HandleDiscoveredTests(IEnumerable discoveredTestCases) - { - _logger.Debug("Discovery tests"); + logger.LogDebug("Discovery tests"); - if (discoveredTestCases == null) return; + if (discoveredTestCases == null) + { + return; + } DiscoveredTestCases.AddRange(discoveredTestCases); - _logger.Debug( - "Added test cases: {@TestCases}", - discoveredTestCases.Select(t => t.FullyQualifiedName)); + logger.LogDebug("Added test cases: {@TestCases}", discoveredTestCases.Select(t => t.FullyQualifiedName)); } public void HandleDiscoveryComplete(long totalTests, IEnumerable? lastChunk, bool isAborted) @@ -39,14 +30,19 @@ public void HandleDiscoveryComplete(long totalTests, IEnumerable? last DiscoveredTestCases.AddRange(lastChunk); } - _logger.Debug("Discovery completed"); + logger.LogDebug("Discovery completed"); + + _ = waitHandle.Set(); + } - waitHandle.Set(); + public void WaitForEnd() + { + _ = waitHandle.WaitOne(); } - public void HandleLogMessage(TestMessageLevel level, string message) + public void HandleLogMessage(TestMessageLevel level, string? message) { - _logger.Debug("Discovery Message: {Message}", message); + logger.LogDebug("Discovery Message: {Message}", message); } public void HandleRawMessage(string rawMessage) diff --git a/TmsRunner/Handlers/RunEventHandler.cs b/TmsRunner/Handlers/RunEventHandler.cs index a1f8ead..e1a1dbb 100644 --- a/TmsRunner/Handlers/RunEventHandler.cs +++ b/TmsRunner/Handlers/RunEventHandler.cs @@ -1,52 +1,40 @@ +using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; -using Serilog; -using TmsRunner.Logger; +using TmsRunner.Services; namespace TmsRunner.Handlers; -public class RunEventHandler : ITestRunEventsHandler2 +public sealed class RunEventHandler(ILogger logger, AutoResetEvent waitHandle, ProcessorService processorService) : ITestRunEventsHandler { - private AutoResetEvent waitHandle; - private readonly ILogger _logger; + public List ProcessTestResultsTasks { get; } = []; - public List TestResults { get;} - - public RunEventHandler(AutoResetEvent waitHandle) - { - this.waitHandle = waitHandle; - TestResults = new List(); - - _logger = LoggerFactory.GetLogger().ForContext(); - } - - public void HandleLogMessage(TestMessageLevel level, string message) + public void HandleLogMessage(TestMessageLevel level, string? message) { - _logger.Debug("Run Message: {Message}", message); + logger.LogDebug("Run Message: {Message}", message); } public void HandleTestRunComplete( TestRunCompleteEventArgs testRunCompleteArgs, - TestRunChangedEventArgs lastChunkArgs, - ICollection runContextAttachments, - ICollection executorUris) + TestRunChangedEventArgs? lastChunkArgs, + ICollection? runContextAttachments, + ICollection? executorUris) { if (lastChunkArgs != null && lastChunkArgs.NewTestResults != null) { - TestResults.AddRange(lastChunkArgs.NewTestResults); + ProcessTestResultsTasks.Add(ProcessTestResultsAsync(lastChunkArgs.NewTestResults)); } - _logger.Debug("Test Run completed"); - - waitHandle.Set(); + logger.LogDebug("Test Run completed"); + _ = waitHandle.Set(); } - public void HandleTestRunStatsChange(TestRunChangedEventArgs testRunChangedArgs) + public void HandleTestRunStatsChange(TestRunChangedEventArgs? testRunChangedArgs) { if (testRunChangedArgs != null && testRunChangedArgs.NewTestResults != null) { - TestResults.AddRange(testRunChangedArgs.NewTestResults); + ProcessTestResultsTasks.Add(ProcessTestResultsAsync(testRunChangedArgs.NewTestResults)); } } @@ -66,4 +54,38 @@ public bool AttachDebuggerToProcess(int pid) // No op return false; } + + public void WaitForEnd() + { + _ = waitHandle.WaitOne(); + } + + private async Task ProcessTestResultsAsync(IEnumerable? testResults) + { + if (testResults == null || !testResults.Any()) + { + return; + } + + foreach (var testResult in testResults) + { + if (testResult == null) + { + continue; + } + + logger.LogInformation("Uploading test {Name}", testResult.DisplayName); + + try + { + await processorService.ProcessAutoTestAsync(testResult).ConfigureAwait(false); + + logger.LogInformation("Uploaded test {Name}", testResult.DisplayName); + } + catch (Exception e) + { + logger.LogError(e, "Uploaded test {Name} is failed", testResult.DisplayName); + } + } + } } \ No newline at end of file diff --git a/TmsRunner/Logger/LoggerFactory.cs b/TmsRunner/Logger/LoggerFactory.cs deleted file mode 100644 index 65c55ff..0000000 --- a/TmsRunner/Logger/LoggerFactory.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Serilog; - -namespace TmsRunner.Logger; - -public static class LoggerFactory -{ - private static ILogger _logger; - private static readonly object Lock = new object(); - - public static ILogger GetLogger(bool isDebug = false) - { - if (_logger != null) return _logger; - - lock (Lock) - { - if (_logger != null) return _logger; - - var logConfig = isDebug - ? new LoggerConfiguration() - .MinimumLevel.Debug() - : new LoggerConfiguration(); - - _logger = logConfig - .Enrich.FromLogContext() - .Enrich.WithThreadId() - .WriteTo.Console( - outputTemplate: - "{Timestamp:HH:mm} [{Level}] ({ThreadId}) {SourceContext}: {Message}{NewLine}{Exception}") - .CreateLogger(); - - return _logger; - } - } -} \ No newline at end of file diff --git a/TmsRunner/Models/AutoTest.cs b/TmsRunner/Models/AutoTest.cs index e5196c2..308fd3b 100644 --- a/TmsRunner/Models/AutoTest.cs +++ b/TmsRunner/Models/AutoTest.cs @@ -1,23 +1,22 @@ using Tms.Adapter.Models; -namespace TmsRunner.Models +namespace TmsRunner.Models; + +public sealed record AutoTest { - public class AutoTest - { - public string Namespace { get; set; } - public string Classname { get; set; } - public List Steps { get; set; } = new(); - public List Setup { get; set; } - public List Teardown { get; set; } - public string ExternalId { get; set; } - public string Name { get; set; } - public string Title { get; set; } - public string Description { get; set; } - public List WorkItemIds { get; set; } = new(); - public List? Links { get; set; } = new(); - public List? Labels { get; set; } - public string MethodName { get; set; } - public string? Message { get; set; } - public bool? IsFlaky { get; set; } - } + public string? Namespace { get; set; } + public string? Classname { get; set; } + public List Steps { get; set; } = []; + public List? Setup { get; set; } + public List? Teardown { get; set; } + public string? ExternalId { get; set; } + public string? Name { get; set; } + public string? Title { get; set; } + public string? Description { get; set; } + public List WorkItemIds { get; set; } = []; + public List? Links { get; set; } = []; + public List? Labels { get; set; } + public string? MethodName { get; set; } + public string? Message { get; set; } + public bool? IsFlaky { get; set; } } \ No newline at end of file diff --git a/TmsRunner/Models/AutoTestResult.cs b/TmsRunner/Models/AutoTestResult.cs index c3ec7b1..b2105f6 100644 --- a/TmsRunner/Models/AutoTestResult.cs +++ b/TmsRunner/Models/AutoTestResult.cs @@ -3,19 +3,19 @@ namespace TmsRunner.Models; -public class AutoTestResult +public sealed record AutoTestResult { public List? Links { get; set; } - public string Message { get; set; } - public string ExternalId { get; set; } - public TestOutcome Outcome { get; set; } - public string Traces { get; set; } + public string? Message { get; set; } + public string? ExternalId { get; set; } + public TestOutcome? Outcome { get; set; } + public string? Traces { get; set; } public DateTime? StartedOn { get; set; } public DateTime? CompletedOn { get; set; } public long? Duration { get; set; } - public List Attachments { get; set; } - public Dictionary Parameters { get; set; } - public List StepResults { get; set; } - public List SetupResults { get; set; } - public List TeardownResults { get; set; } + public List? Attachments { get; set; } + public Dictionary? Parameters { get; set; } + public List? StepResults { get; set; } + public List? SetupResults { get; set; } + public List? TeardownResults { get; set; } } \ No newline at end of file diff --git a/TmsRunner/Models/AutoTestStep.cs b/TmsRunner/Models/AutoTestStep.cs index 48e681a..1995ed4 100644 --- a/TmsRunner/Models/AutoTestStep.cs +++ b/TmsRunner/Models/AutoTestStep.cs @@ -1,17 +1,17 @@ namespace TmsRunner.Models; -public class AutoTestStep +public sealed record AutoTestStep { - public string Title { get; set; } - public string Description { get; set; } - public List Steps { get; set; } + public string? Title { get; set; } + public string? Description { get; set; } + public List? Steps { get; set; } public static AutoTestStep ConvertFromStep(Step step) { return new AutoTestStep { - Title = step.Title, - Description = step.Description, + Title = step.Title ?? string.Empty, + Description = step.Description ?? string.Empty, Steps = step.Steps.Select(ConvertFromStep).ToList() }; } diff --git a/TmsRunner/Models/AutoTestStepResult.cs b/TmsRunner/Models/AutoTestStepResult.cs index c98e2f5..ee457b2 100644 --- a/TmsRunner/Models/AutoTestStepResult.cs +++ b/TmsRunner/Models/AutoTestStepResult.cs @@ -1,16 +1,16 @@ namespace TmsRunner.Models; -public class AutoTestStepResult +public sealed record AutoTestStepResult { public string? Title { get; set; } public string? Description { get; set; } public DateTime? StartedOn { get; set; } public DateTime? CompletedOn { get; set; } public long? Duration { get; set; } - public List Attachments { get; set; } + public List? Attachments { get; set; } public Dictionary? Parameters { get; set; } - public List Steps { get; set; } - public string Outcome { get; set; } + public List? Steps { get; set; } + public string? Outcome { get; set; } public static AutoTestStepResult ConvertFromStep(Step step) { diff --git a/TmsRunner/Models/Config.cs b/TmsRunner/Models/Config.cs index 6ea55a3..69abe78 100644 --- a/TmsRunner/Models/Config.cs +++ b/TmsRunner/Models/Config.cs @@ -1,28 +1,28 @@ namespace TmsRunner.Models; -public class Config +public sealed record Config { - public string TmsUrl { get; set; } + public string? TmsUrl { get; set; } - public string TmsPrivateToken { get; set; } + public string? TmsPrivateToken { get; set; } - public string TmsProjectId { get; set; } + public string? TmsProjectId { get; set; } - public string TmsConfigurationId { get; set; } + public string? TmsConfigurationId { get; set; } - public string TmsTestRunId { get; set; } + public string? TmsTestRunId { get; set; } - public string TmsTestRunName { get; set; } + public string? TmsTestRunName { get; set; } - public string TmsAdapterMode { get; set; } + public string? TmsAdapterMode { get; set; } - public string TmsConfigFile { get; set; } + public string? TmsConfigFile { get; set; } - public string TmsRunSettings { get; set; } - - public string TmsAutomaticCreationTestCases { get; set; } + public string? TmsRunSettings { get; set; } - public string TmsCertValidation { get; set; } + public string? TmsAutomaticCreationTestCases { get; set; } - public string TmsLabelsOfTestsToRun { get; set; } + public string? TmsCertValidation { get; set; } + + public string? TmsLabelsOfTestsToRun { get; set; } } \ No newline at end of file diff --git a/TmsRunner/Models/MessageMetadata.cs b/TmsRunner/Models/MessageMetadata.cs index ba80098..ea0ed79 100644 --- a/TmsRunner/Models/MessageMetadata.cs +++ b/TmsRunner/Models/MessageMetadata.cs @@ -2,8 +2,8 @@ namespace TmsRunner.Models; -public class MessageMetadata +public sealed record MessageMetadata { public MessageType Type { get; set; } - public string Value { get; set; } + public string? Value { get; set; } } \ No newline at end of file diff --git a/TmsRunner/Models/MethodMetadata.cs b/TmsRunner/Models/MethodMetadata.cs index 9e2b48f..4e6fdf1 100644 --- a/TmsRunner/Models/MethodMetadata.cs +++ b/TmsRunner/Models/MethodMetadata.cs @@ -1,9 +1,9 @@ namespace TmsRunner.Models; -public class MethodMetadata +public sealed record MethodMetadata { - public string Name { get; set; } - public string Namespace { get; set; } - public string Classname { get; set; } - public List Attributes { get; set; } -} \ No newline at end of file + public string? Name { get; set; } + public string? Namespace { get; set; } + public string? Classname { get; set; } + public List? Attributes { get; set; } +} \ No newline at end of file diff --git a/TmsRunner/Models/Step.cs b/TmsRunner/Models/Step.cs index e51332a..1fa381a 100644 --- a/TmsRunner/Models/Step.cs +++ b/TmsRunner/Models/Step.cs @@ -1,39 +1,41 @@ using System.Text; using Tms.Adapter.Models; -namespace TmsRunner.Models +namespace TmsRunner.Models; + +public sealed class Step : StepDto { - public class Step : StepDto + public string? Result { get; set; } + public DateTime? CompletedOn { get; set; } + public long Duration { get; set; } + public List Steps { get; set; } = []; + public Step? ParentStep { get; set; } + public int NestingLevel { get; set; } + public List Links { get; set; } = []; + public List Attachments { get; set; } = []; + public string? Outcome { get; set; } + + private string? _stackTrace; + + public string StackTrace() { - public string? Result { get; set; } - public DateTime? CompletedOn { get; set; } - public long Duration { get; set; } - public List Steps { get; set; } = new(); - public Step? ParentStep { get; set; } - public int NestingLevel { get; set; } - public List Links { get; set; } = new(); - public List Attachments { get; set; } = new(); - public string Outcome { get; set; } - - string _stackTrace; - - public string StackTrace() + if (_stackTrace != null) { - if (_stackTrace != null) return _stackTrace; + return _stackTrace; + } - var sb = new StringBuilder(); - var parent = ParentStep; - sb.Append(CurrentMethod); + var sb = new StringBuilder(); + var parent = ParentStep; + _ = sb.Append(CurrentMethod); - while (parent != null) - { - sb.Insert(0, parent.CurrentMethod + Environment.NewLine); - parent = parent.ParentStep; - } + while (parent != null) + { + _ = sb.Insert(0, parent.CurrentMethod + Environment.NewLine); + parent = parent.ParentStep; + } - _stackTrace = sb.ToString(); + _stackTrace = sb.ToString(); - return _stackTrace; - } + return _stackTrace; } } \ No newline at end of file diff --git a/TmsRunner/Models/TmsSettings.cs b/TmsRunner/Models/TmsSettings.cs index 5e60c84..5ed9f63 100644 --- a/TmsRunner/Models/TmsSettings.cs +++ b/TmsRunner/Models/TmsSettings.cs @@ -1,6 +1,6 @@ namespace TmsRunner.Models; -public class TmsSettings +public sealed class TmsSettings { private string? _url; @@ -10,13 +10,13 @@ public string? Url set => _url = value; } - public string PrivateToken { get; set; } - public string ProjectId { get; set; } - public string ConfigurationId { get; set; } - public string TestRunId { get; set; } - public string TestRunName { get; set; } + public string? PrivateToken { get; set; } + public string? ProjectId { get; set; } + public string? ConfigurationId { get; set; } + public string? TestRunId { get; set; } + public string? TestRunName { get; set; } public int AdapterMode { get; set; } - public string RunSettings { get; set; } + public string? RunSettings { get; set; } public bool AutomaticCreationTestCases { get; set; } public bool CertValidation { get; set; } } \ No newline at end of file diff --git a/TmsRunner/Program.cs b/TmsRunner/Program.cs index f0d6367..40c3173 100644 --- a/TmsRunner/Program.cs +++ b/TmsRunner/Program.cs @@ -1,116 +1,49 @@ using CommandLine; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.TestPlatform.VsTestConsole.TranslationLayer; +using Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; +using Polly; +using Polly.Extensions.Http; +using Polly.Retry; +using Serilog; +using Serilog.Events; +using Serilog.Expressions; +using Serilog.Settings.Configuration; +using System.Net; +using TestIT.ApiClient.Api; using Tms.Adapter.Utils; using TmsRunner.Client; using TmsRunner.Configuration; +using TmsRunner.Enums; using TmsRunner.Extensions; -using TmsRunner.Logger; -using TmsRunner.Options; +using TmsRunner.Handlers; +using TmsRunner.Models; using TmsRunner.Services; using TmsRunner.Utils; +using ConfigurationManager = TmsRunner.Configuration.ConfigurationManager; namespace TmsRunner; -internal class Program +public class Program { public static async Task Main(string[] args) { - var config = GetAdapterConfiguration(args); - var settings = ConfigurationManager.Configure(config.ToInternalConfig(), - Path.GetDirectoryName(config.TestAssemblyPath)!); - - var log = LoggerFactory.GetLogger(config.IsDebug).ForContext(); - - log.Information("Adapter works in {Mode} mode", settings.AdapterMode); - log.Debug("Parameters:"); - log.Debug("Runner Path: {Path}", config.RunnerPath); - log.Debug("Test Assembly Path: {Path}", config.TestAssemblyPath); - log.Debug("Test Adapter Path: {Path}", config.TestAdapterPath); - log.Debug("Test Logger Path: {Path}", config.LoggerPath); - - var runner = new Runner(config); - runner.InitialiseRunner(); - - var testCases = runner.DiscoverTests(); - - log.Information("Discovered Tests Count: {Count}", testCases.Count); - - if (testCases.Count == 0) - { - log.Information("Can not found tests for run"); - return 1; - } - - ITmsClient apiClient = new TmsClient(settings); - - var replacer = new Replacer(); - var filterService = new FilterService(replacer); - - switch (settings.AdapterMode) - { - case 0: - { - var testCaseForRun = await apiClient.GetAutoTestsForRun(settings.TestRunId); - testCases = filterService.FilterTestCases(config.TestAssemblyPath, testCaseForRun, testCases); - break; - } - case 2: - { - settings.TestRunId = await apiClient.CreateTestRun(); - - if (!string.IsNullOrEmpty(config.TmsLabelsOfTestsToRun)) - { - testCases = filterService.FilterTestCasesByLabels(config, testCases); - } - - break; - } - } - - log.Information("Running tests: {Count}", testCases.Count); - - var testResults = runner.RunSelectedTests(testCases); - - log.Debug("Run Selected Test Result: {@Results}", - testResults.Select(t => t.DisplayName)); - - var reflector = new Reflector(); - var parser = new LogParser(replacer, reflector); - var processorService = - new ProcessorService(apiClient, settings, parser); - - foreach (var testResult in testResults) - { - log.Information("Uploading test {Name}", testResult.DisplayName); - - try - { - await processorService.ProcessAutoTest(testResult); - - log.Information("Uploaded test {Name}", testResult.DisplayName); - } - catch (Exception e) - { - log.Error(e, "Uploaded test {Name} is failed", testResult.DisplayName); - } - } - - if (settings.AdapterMode == 2) - log.Information("Test run {TestRunId} finished.", settings.TestRunId); - - return 0; + using var host = CreateHostBuilder(args).Build(); + return await host.Services.GetRequiredService().RunAsync().ConfigureAwait(false); } private static AdapterConfig GetAdapterConfiguration(IEnumerable args) { AdapterConfig config = null!; - Parser.Default.ParseArguments(args) + _ = Parser.Default.ParseArguments(args) .WithParsed(ac => { config = new AdapterConfig { - RunnerPath = ac.RunnerPath.RemoveQuotes(), - TestAssemblyPath = ac.TestAssemblyPath.RemoveQuotes(), + RunnerPath = ac.RunnerPath?.RemoveQuotes(), + TestAssemblyPath = ac.TestAssemblyPath?.RemoveQuotes(), TestAdapterPath = ac.TestAdapterPath?.RemoveQuotes() ?? string.Empty, LoggerPath = ac.LoggerPath?.RemoveQuotes() ?? string.Empty, IsDebug = ac.IsDebug, @@ -125,7 +58,108 @@ private static AdapterConfig GetAdapterConfiguration(IEnumerable args) TmsLabelsOfTestsToRun = ac.TmsLabelsOfTestsToRun }; }); - + + if (string.IsNullOrWhiteSpace(config.TmsRunSettings)) + { + config.TmsRunSettings = + @" + + + + +"; + } + return config; } + + private static IHostBuilder CreateHostBuilder(string[] args) + { + var options = new ConfigurationReaderOptions( + typeof(ConsoleLoggerConfigurationExtensions).Assembly, + typeof(SerilogExpression).Assembly + ); + + return Host.CreateDefaultBuilder() + .UseSerilog((context, services, configuration) => configuration + .ReadFrom.Configuration(context.Configuration, options) + .ReadFrom.Services(services) + .Enrich.FromLogContext() + .MinimumLevel.Debug() + .WriteTo.Console(LogEventLevel.Information)) + .ConfigureServices((hostContext, services) => + { + _ = services.AddHttpClient(nameof(HttpClientNames.Default), client => + { + client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher; + client.DefaultRequestVersion = HttpVersion.Version11; + }) + .SetHandlerLifetime(TimeSpan.FromDays(1)) + .AddPolicyHandler(GetRetryPolicy()); + + _ = services + .AddSingleton(GetAdapterConfiguration(args)) + .AddSingleton(provider => + { + var adapterConfig = provider.GetRequiredService(); + + return ConfigurationManager.Configure( + adapterConfig.ToInternalConfig(), + Path.GetDirectoryName(adapterConfig.TestAssemblyPath) ?? string.Empty + ); + }) + .AddSingleton(provider => + { + var tmsSettings = provider.GetRequiredService(); + + return new TestIT.ApiClient.Client.Configuration + { + BasePath = tmsSettings.Url ?? string.Empty, + ApiKeyPrefix = new Dictionary { { "Authorization", "PrivateToken" } }, + ApiKey = new Dictionary { { "Authorization", tmsSettings?.PrivateToken ?? string.Empty } } + }; + }) + .AddTransient(provider => new HttpClientHandler + { + ServerCertificateCustomValidationCallback = (_, _, _, _) => provider.GetRequiredService().CertValidation + }) + .AddTransient(provider => new AttachmentsApi( + provider.GetRequiredService().CreateClient(nameof(HttpClientNames.Default)), + provider.GetRequiredService(), + provider.GetRequiredService() + )) + .AddTransient(provider => new TestRunsApi( + provider.GetRequiredService().CreateClient(nameof(HttpClientNames.Default)), + provider.GetRequiredService(), + provider.GetRequiredService() + )) + .AddTransient(provider => new AutoTestsApi( + provider.GetRequiredService().CreateClient(nameof(HttpClientNames.Default)), + provider.GetRequiredService(), + provider.GetRequiredService() + )) + .AddTransient() + .AddTransient() + .AddTransient() + .AddTransient() + .AddTransient() + .AddTransient() + .AddTransient() + .AddTransient(provider => new AutoResetEvent(false)) + .AddTransient() + .AddTransient() + .AddTransient(provider => new VsTestConsoleWrapper( + provider.GetRequiredService().RunnerPath ?? string.Empty, + new ConsoleParameters { LogFilePath = Path.Combine(Directory.GetCurrentDirectory(), @"log.txt") } + )) + .AddSingleton(); + }); + } + + private static AsyncRetryPolicy GetRetryPolicy() + { + return HttpPolicyExtensions + .HandleTransientHttpError() + .WaitAndRetryAsync(2, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); + } } \ No newline at end of file diff --git a/TmsRunner/Runner.cs b/TmsRunner/Runner.cs deleted file mode 100644 index 6e65d32..0000000 --- a/TmsRunner/Runner.cs +++ /dev/null @@ -1,86 +0,0 @@ -using Microsoft.TestPlatform.VsTestConsole.TranslationLayer; -using Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Serilog; -using TmsRunner.Handlers; -using TmsRunner.Logger; -using TmsRunner.Options; - -namespace TmsRunner; - -public class Runner -{ - private const string DefaultRunSettings = - @" - - - - -"; - - private readonly AdapterConfig _config; - private readonly ILogger _logger; - private readonly IVsTestConsoleWrapper _consoleWrapper; - private readonly string _runSettings; - - public Runner(AdapterConfig config) - { - _config = config; - _logger = LoggerFactory.GetLogger().ForContext(); - _consoleWrapper = new VsTestConsoleWrapper(config.RunnerPath, - new ConsoleParameters { LogFilePath = Path.Combine(Directory.GetCurrentDirectory(), @"log.txt") }); - _runSettings = string.IsNullOrWhiteSpace(_config.TmsRunSettings) ? DefaultRunSettings : _config.TmsRunSettings; - } - - public void InitialiseRunner() - { - _consoleWrapper.StartSession(); - - _logger.Information("Start session"); - - var extensions = new List(); - - if (File.Exists(_config.TestAdapterPath)) - { - extensions.Add(_config.TestAdapterPath); - - _logger.Debug("Added test adapter extension"); - } - - if (File.Exists(_config.LoggerPath)) - { - extensions.Add(_config.LoggerPath); - - _logger.Debug("Added logger extension"); - } - - if (extensions.Count > 0) - { - _consoleWrapper.InitializeExtensions(extensions); - } - } - - public List DiscoverTests() - { - var waitHandle = new AutoResetEvent(false); - var handler = new DiscoveryEventHandler(waitHandle); - - _consoleWrapper.DiscoverTests(new List { _config.TestAssemblyPath }, _runSettings, handler); - - waitHandle.WaitOne(); - - return handler.DiscoveredTestCases; - } - - public List RunSelectedTests(IEnumerable testCases) - { - var waitHandle = new AutoResetEvent(false); - var handler = new RunEventHandler(waitHandle); - - _consoleWrapper.RunTests(testCases, _runSettings, handler); - - waitHandle.WaitOne(); - - return handler.TestResults; - } -} \ No newline at end of file diff --git a/TmsRunner/Services/FilterService.cs b/TmsRunner/Services/FilterService.cs index 6877e06..757f56f 100644 --- a/TmsRunner/Services/FilterService.cs +++ b/TmsRunner/Services/FilterService.cs @@ -1,36 +1,27 @@ +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; using System.Data; using System.Reflection; using System.Text.RegularExpressions; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Serilog; using Tms.Adapter.Attributes; using Tms.Adapter.Utils; +using TmsRunner.Configuration; using TmsRunner.Extensions; -using TmsRunner.Logger; -using TmsRunner.Options; namespace TmsRunner.Services; -public class FilterService +public sealed class FilterService(ILogger logger, Replacer replacer) { - private readonly Replacer _replacer; - private readonly ILogger _log; - private static readonly Regex _parametersRegex = new Regex("\\((.*)\\)"); - - public FilterService(Replacer replacer) - { - _replacer = replacer; - _log = LoggerFactory.GetLogger(false).ForContext(); - } + private static readonly Regex _parametersRegex = new("\\((.*)\\)"); // TODO: write unit tests public List FilterTestCases( - string assemblyPath, - IEnumerable externalIds, + string? assemblyPath, + IEnumerable? externalIds, IEnumerable testCases) { var testCasesToRun = new List(); - var assembly = Assembly.LoadFrom(assemblyPath); + var assembly = Assembly.LoadFrom(assemblyPath ?? string.Empty); var allTestMethods = new List(assembly.GetExportedTypes().SelectMany(type => type.GetMethods())); foreach (var testCase in testCases) @@ -41,13 +32,13 @@ public List FilterTestCases( if (testMethod == null) { - _log.Error("TestMethod {@FullyQualifiedName} not found", testCase.FullyQualifiedName); + logger.LogError("TestMethod {@FullyQualifiedName} not found", testCase.FullyQualifiedName); continue; } var externalId = GetExternalId(testCase, testMethod); - if (externalIds.Contains(externalId)) + if (externalIds?.Contains(externalId) ?? false) { testCasesToRun.Add(testCase); } @@ -67,9 +58,10 @@ private string GetExternalId(TestCase testCase, MethodInfo testMethod) var parameterNames = testMethod.GetParameters().Select(x => x.Name?.ToString()); var parameterValues = _parametersRegex.Match(testCase.DisplayName).Groups[1].Value.Split(',').Select(x => x.Replace("\"", string.Empty)); var parameterDictionary = parameterNames + .Select(x => x ?? string.Empty) .Zip(parameterValues, (k, v) => new { k, v }) .ToDictionary(x => x.k, x => x.v); - return _replacer.ReplaceParameters(externalId.Value, parameterDictionary!); + return replacer.ReplaceParameters(externalId.Value, parameterDictionary!); } } @@ -80,10 +72,10 @@ public List FilterTestCasesByLabels( AdapterConfig config, IEnumerable testCases) { - var labelsToRun = config.TmsLabelsOfTestsToRun.Split(',').Select(x => x.Trim()).ToList(); + var labelsToRun = config.TmsLabelsOfTestsToRun?.Split(',').Select(x => x.Trim()).ToList(); var testCasesName = testCases.Select(t => t.FullyQualifiedName); var testCasesToRun = new List(); - var assembly = Assembly.LoadFrom(config.TestAssemblyPath); + var assembly = Assembly.LoadFrom(config.TestAssemblyPath ?? string.Empty); var testMethods = new List( assembly.GetExportedTypes() .SelectMany(type => type.GetMethods()) @@ -92,18 +84,24 @@ public List FilterTestCasesByLabels( foreach (var testMethod in testMethods) { - var fullName = testMethod.DeclaringType!.FullName + "." + testMethod.Name; + var fullName = testMethod?.DeclaringType?.FullName + "." + testMethod?.Name; + var customAttributes = testMethod?.GetCustomAttributes(false) ?? []; - foreach (var attribute in testMethod.GetCustomAttributes(false)) + foreach (var attribute in customAttributes) { if (attribute is LabelsAttribute labelsAttr) { - if (labelsAttr.Value.Any(labelsToRun.Contains)) + if (labelsAttr.Value?.Any(x => labelsToRun?.Contains(x) ?? false) ?? false) { - testCasesToRun.Add(testCases.FirstOrDefault(x => x.FullyQualifiedName == fullName)); + var testCase = testCases.FirstOrDefault(x => x.FullyQualifiedName == fullName); + + if (testCase != null) + { + testCasesToRun.Add(testCase); + } } } - } + } } return testCasesToRun; diff --git a/TmsRunner/Services/ProcessorService.cs b/TmsRunner/Services/ProcessorService.cs index 471e575..0016202 100644 --- a/TmsRunner/Services/ProcessorService.cs +++ b/TmsRunner/Services/ProcessorService.cs @@ -1,53 +1,36 @@ -using System.Text; +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using System.Text; using System.Text.Json; using System.Text.RegularExpressions; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Serilog; using Tms.Adapter.Models; using TmsRunner.Client; -using TmsRunner.Logger; using TmsRunner.Models; using TmsRunner.Utils; using File = Tms.Adapter.Models.File; -namespace TmsRunner.Services +namespace TmsRunner.Services; + +public sealed class ProcessorService(ILogger logger, ITmsClient apiClient, TmsSettings tmsSettings, LogParser parser) { - public class ProcessorService + private async Task> GetStepsWithAttachmentsAsync(string? traceJson, ICollection attachmentIds) { - private readonly ITmsClient _apiClient; - private readonly TmsSettings _tmsSettings; - private readonly LogParser _parser; - private readonly ILogger _logger = LoggerFactory.GetLogger().ForContext(); - - public ProcessorService( - ITmsClient apiClient, - TmsSettings tmsSettings, - LogParser parser) - { - _apiClient = apiClient; - _tmsSettings = tmsSettings; - _parser = parser; - } - - private async Task> GetStepsWithAttachments( - string? traceJson, ICollection attachmentIds) - { - var messages = _parser.GetMessages(traceJson); + var messages = parser.GetMessages(traceJson ?? string.Empty); - var testCaseStepsHierarchical = new List(); - Step? parentStep = null; - var nestingLevel = 1; + var testCaseStepsHierarchical = new List(); + Step? parentStep = null; + var nestingLevel = 1; - foreach (var message in messages) + foreach (var message in messages) + { + switch (message.Type) { - switch (message.Type) - { - case MessageType.TmsStep: + case MessageType.TmsStep: { - var step = JsonSerializer.Deserialize(message.Value); + var step = JsonSerializer.Deserialize(message?.Value ?? string.Empty); if (step == null) { - _logger.Warning("Can not deserialize step: {Step}", message.Value); + logger.LogWarning("Can not deserialize step: {Step}", message?.Value); break; } @@ -89,13 +72,13 @@ private async Task> GetStepsWithAttachments( break; } - case MessageType.TmsStepResult: + case MessageType.TmsStepResult: { - var stepResult = JsonSerializer.Deserialize(message.Value); + var stepResult = JsonSerializer.Deserialize(message?.Value ?? string.Empty); if (stepResult == null) { - _logger.Warning("Can not deserialize step result: {StepResult}", message.Value); + logger.LogWarning("Can not deserialize step result: {StepResult}", message?.Value ?? string.Empty); break; } @@ -108,12 +91,12 @@ private async Task> GetStepsWithAttachments( break; } - case MessageType.TmsStepAttachmentAsText: + case MessageType.TmsStepAttachmentAsText: { - var attachment = JsonSerializer.Deserialize(message.Value); + var attachment = JsonSerializer.Deserialize(message?.Value ?? string.Empty); using var ms = new MemoryStream(Encoding.UTF8.GetBytes(attachment!.Content)); var createdAttachment = - await _apiClient.UploadAttachment(Path.GetFileName(attachment.Name), ms); + await apiClient.UploadAttachmentAsync(Path.GetFileName(attachment.Name), ms).ConfigureAwait(false); if (parentStep is not null) { @@ -129,14 +112,14 @@ private async Task> GetStepsWithAttachments( break; } - case MessageType.TmsStepAttachment: + case MessageType.TmsStepAttachment: { - var file = JsonSerializer.Deserialize(message.Value); + var file = JsonSerializer.Deserialize(message?.Value ?? string.Empty); if (System.IO.File.Exists(file!.PathToFile)) { using var fs = new FileStream(file.PathToFile, FileMode.Open, FileAccess.Read); - var attachment = await _apiClient.UploadAttachment(Path.GetFileName(file.PathToFile), fs); + var attachment = await apiClient.UploadAttachmentAsync(Path.GetFileName(file.PathToFile), fs).ConfigureAwait(false); if (parentStep is not null) { @@ -153,150 +136,151 @@ private async Task> GetStepsWithAttachments( break; } - default: - _logger.Debug("Un support message type: {MessageType}", message.Type); - break; - } + default: + logger.LogDebug("Un support message type: {MessageType}", message.Type); + break; } - - return testCaseStepsHierarchical; } - private static string? GetCalledMethod(string? calledMethod) - { - if (calledMethod == null || !calledMethod.Contains("<")) return calledMethod; - - const string pattern = "(?<=\\<)(.*)(?=\\>)"; - var regex = new Regex(pattern); - var match = regex.Match(calledMethod); + return testCaseStepsHierarchical; + } - return match.Groups[1].Value; + private static string? GetCalledMethod(string? calledMethod) + { + if (calledMethod == null || !calledMethod.Contains('<')) + { + return calledMethod; } - public async Task ProcessAutoTest(TestResult testResult) - { - var traceJson = GetTraceJson(testResult); - var parameters = _parser.GetParameters(traceJson); - var autoTest = _parser.GetAutoTest(testResult, parameters); - autoTest.Message = _parser.GetMessage(traceJson); + const string pattern = "(?<=\\<)(.*)(?=\\>)"; + var regex = new Regex(pattern); + var match = regex.Match(calledMethod); - var attachmentIds = new List(); - var testCaseSteps = - await GetStepsWithAttachments(traceJson, attachmentIds); + return match.Groups[1].Value; + } - autoTest.Setup = testCaseSteps - .Where(x => x.CallerMethodType == CallerMethodType.Setup) - .Select(AutoTestStep.ConvertFromStep) - .ToList(); + public async Task ProcessAutoTestAsync(TestResult testResult) + { + var traceJson = GetTraceJson(testResult); + var parameters = parser.GetParameters(traceJson); + var autoTest = parser.GetAutoTest(testResult, parameters); + autoTest.Message = LogParser.GetMessage(traceJson); - autoTest.Steps = testCaseSteps - .Where(x => x.CallerMethodType == CallerMethodType.TestMethod) - .Select(AutoTestStep.ConvertFromStep) - .ToList(); + var attachmentIds = new List(); + var testCaseSteps = await GetStepsWithAttachmentsAsync(traceJson, attachmentIds).ConfigureAwait(false); - autoTest.Teardown = testCaseSteps - .Where(x => x.CallerMethodType == CallerMethodType.Teardown) - .Select(AutoTestStep.ConvertFromStep) - .ToList(); + autoTest.Setup = testCaseSteps + .Where(x => x.CallerMethodType == CallerMethodType.Setup) + .Select(AutoTestStep.ConvertFromStep) + .ToList(); + autoTest.Steps = testCaseSteps + .Where(x => x.CallerMethodType == CallerMethodType.TestMethod) + .Select(AutoTestStep.ConvertFromStep) + .ToList(); - var existAutotest = await _apiClient.GetAutotestByExternalId(autoTest.ExternalId); + autoTest.Teardown = testCaseSteps + .Where(x => x.CallerMethodType == CallerMethodType.Teardown) + .Select(AutoTestStep.ConvertFromStep) + .ToList(); - if (existAutotest == null) - { - existAutotest = await _apiClient.CreateAutotest(autoTest); - } - else - { - autoTest.IsFlaky = existAutotest.IsFlaky; - await _apiClient.UpdateAutotest(autoTest); - } + var existAutotest = await apiClient.GetAutotestByExternalIdAsync(autoTest.ExternalId).ConfigureAwait(false); - if (autoTest.WorkItemIds.Count > 0) - { - if (!await _apiClient.TryLinkAutoTestToWorkItem(existAutotest.Id.ToString(), autoTest.WorkItemIds)) - { - return; - } - } + if (existAutotest == null) + { + existAutotest = await apiClient.CreateAutotestAsync(autoTest).ConfigureAwait(false); + } + else + { + autoTest.IsFlaky = existAutotest.IsFlaky; - if (!string.IsNullOrEmpty(testResult.ErrorMessage)) + await apiClient.UpdateAutotestAsync(autoTest).ConfigureAwait(false); + } + + if (autoTest.WorkItemIds.Count > 0) + { + if (!await apiClient.TryLinkAutoTestToWorkItemAsync(existAutotest.Id.ToString(), autoTest.WorkItemIds).ConfigureAwait(false)) { - autoTest.Message += Environment.NewLine + testResult.ErrorMessage; + return; } - - var autoTestResultRequestBody = GetAutoTestResultsForTestRunModel(testResult, testCaseSteps, autoTest, - traceJson, parameters, attachmentIds); - - await _apiClient.SubmitResultToTestRun(_tmsSettings.TestRunId, autoTestResultRequestBody); } - private AutoTestResult GetAutoTestResultsForTestRunModel(TestResult testResult, - IReadOnlyCollection testCaseSteps, - AutoTest autoTest, string traceJson, Dictionary? parameters, - List attachmentIds) + if (!string.IsNullOrEmpty(testResult.ErrorMessage)) { - var stepResults = - testCaseSteps - .Where(x => x.CallerMethodType == CallerMethodType.TestMethod) - .Select(AutoTestStepResult.ConvertFromStep).ToList(); + autoTest.Message += Environment.NewLine + testResult.ErrorMessage; + } - var setupResults = - testCaseSteps - .Where(x => x.CallerMethodType == CallerMethodType.Setup) - .Select(AutoTestStepResult.ConvertFromStep).ToList(); + var autoTestResultRequestBody = GetAutoTestResultsForTestRunModel(testResult, testCaseSteps, autoTest, + traceJson, parameters, attachmentIds); - var teardownResults = - testCaseSteps - .Where(x => x.CallerMethodType == CallerMethodType.Teardown) - .Select(AutoTestStepResult.ConvertFromStep).ToList(); + await apiClient.SubmitResultToTestRunAsync(tmsSettings.TestRunId, autoTestResultRequestBody).ConfigureAwait(false); + } + private AutoTestResult GetAutoTestResultsForTestRunModel(TestResult testResult, + IReadOnlyCollection testCaseSteps, + AutoTest autoTest, string traceJson, Dictionary? parameters, + List attachmentIds) + { + var stepResults = + testCaseSteps + .Where(x => x.CallerMethodType == CallerMethodType.TestMethod) + .Select(AutoTestStepResult.ConvertFromStep).ToList(); - var autoTestResultRequestBody = new AutoTestResult - { - ExternalId = autoTest.ExternalId, - Outcome = testResult.Outcome, - StartedOn = testResult.StartTime.UtcDateTime, - CompletedOn = testResult.EndTime.UtcDateTime, - Duration = (long)testResult.Duration.TotalMilliseconds, - StepResults = stepResults, - SetupResults = setupResults, - TeardownResults = teardownResults, - Links = _parser.GetLinks(traceJson), - Message = autoTest.Message!.TrimStart(Environment.NewLine.ToCharArray()), - Parameters = parameters!, - Attachments = attachmentIds, - }; - if (!string.IsNullOrEmpty(testResult.ErrorStackTrace)) - { - autoTestResultRequestBody.Traces = testResult.ErrorStackTrace.TrimStart(); - } + var setupResults = + testCaseSteps + .Where(x => x.CallerMethodType == CallerMethodType.Setup) + .Select(AutoTestStepResult.ConvertFromStep).ToList(); - return autoTestResultRequestBody; - } + var teardownResults = + testCaseSteps + .Where(x => x.CallerMethodType == CallerMethodType.Teardown) + .Select(AutoTestStepResult.ConvertFromStep).ToList(); - private static string GetTraceJson(TestResult testResult) - { - var debugTraceMessages = testResult.Messages.Select(x => x.Text); - var traceJson = string.Join("\n", debugTraceMessages); - return traceJson; + var autoTestResultRequestBody = new AutoTestResult + { + ExternalId = autoTest.ExternalId, + Outcome = testResult.Outcome, + StartedOn = testResult.StartTime.UtcDateTime, + CompletedOn = testResult.EndTime.UtcDateTime, + Duration = (long)testResult.Duration.TotalMilliseconds, + StepResults = stepResults, + SetupResults = setupResults, + TeardownResults = teardownResults, + Links = LogParser.GetLinks(traceJson), + Message = autoTest.Message!.TrimStart(Environment.NewLine.ToCharArray()), + Parameters = parameters!, + Attachments = attachmentIds, + }; + if (!string.IsNullOrEmpty(testResult.ErrorStackTrace)) + { + autoTestResultRequestBody.Traces = testResult.ErrorStackTrace.TrimStart(); } - private static Step? MapStep(Step? step, StepResult stepResult) - { - if (step == null) - { - return null; - } + return autoTestResultRequestBody; + } - step.CompletedOn = stepResult.CompletedOn; - step.Duration = stepResult.Duration; - step.Result = stepResult.Result; - step.Outcome = stepResult.Outcome; + private static string GetTraceJson(TestResult testResult) + { + var debugTraceMessages = testResult.Messages.Select(x => x.Text); + var traceJson = string.Join("\n", debugTraceMessages); + + return traceJson; + } - return step; + private static Step? MapStep(Step? step, StepResult stepResult) + { + if (step == null) + { + return null; } + + step.CompletedOn = stepResult.CompletedOn; + step.Duration = stepResult.Duration; + step.Result = stepResult.Result; + step.Outcome = stepResult.Outcome; + + return step; } } \ No newline at end of file diff --git a/TmsRunner/Services/Runner.cs b/TmsRunner/Services/Runner.cs new file mode 100644 index 0000000..e2b22d1 --- /dev/null +++ b/TmsRunner/Services/Runner.cs @@ -0,0 +1,53 @@ +using Microsoft.Extensions.Logging; +using Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using TmsRunner.Configuration; +using TmsRunner.Handlers; + +namespace TmsRunner.Services; + +public sealed class Runner(ILogger logger, AdapterConfig config, IVsTestConsoleWrapper consoleWrapper, DiscoveryEventHandler discoveryEventHandler, RunEventHandler runEventHandler) +{ + public void InitialiseRunner() + { + consoleWrapper.StartSession(); + + logger.LogInformation("Start session"); + + var extensions = new List(); + + if (File.Exists(config.TestAdapterPath)) + { + extensions.Add(config.TestAdapterPath); + + logger.LogDebug("Added test adapter extension"); + } + + if (File.Exists(config.LoggerPath)) + { + extensions.Add(config.LoggerPath); + + logger.LogDebug("Added logger extension"); + } + + if (extensions.Count > 0) + { + consoleWrapper.InitializeExtensions(extensions); + } + } + + public List DiscoverTests() + { + consoleWrapper.DiscoverTests([config.TestAssemblyPath ?? string.Empty], config.TmsRunSettings, discoveryEventHandler); + discoveryEventHandler.WaitForEnd(); + + return discoveryEventHandler.DiscoveredTestCases; + } + + public async Task RunSelectedTestsAsync(IEnumerable testCases) + { + consoleWrapper.RunTests(testCases, config.TmsRunSettings, runEventHandler); + runEventHandler.WaitForEnd(); + await Task.WhenAll(runEventHandler.ProcessTestResultsTasks).ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/TmsRunner/TmsRunner.csproj b/TmsRunner/TmsRunner.csproj index 17aac8e..9d11e52 100644 --- a/TmsRunner/TmsRunner.csproj +++ b/TmsRunner/TmsRunner.csproj @@ -19,24 +19,19 @@ - - - - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - + + + + + @@ -44,10 +39,6 @@ - - - - Always diff --git a/TmsRunner/Utils/Converter.cs b/TmsRunner/Utils/Converter.cs new file mode 100644 index 0000000..4629a4e --- /dev/null +++ b/TmsRunner/Utils/Converter.cs @@ -0,0 +1,120 @@ +using TestIT.ApiClient.Model; +using TmsRunner.Models; + +namespace TmsRunner.Utils; + +public sealed class Converter +{ + public static CreateAutoTestRequest ConvertAutoTestDtoToPostModel(AutoTest dto, string? projectId) + { + var links = dto.Links?.Select(l => + new LinkPostModel( + l.Title, + l.Url, + l.Description, + Enum.Parse(l.Type.ToString()!)) + ).ToList(); + + return new CreateAutoTestRequest(externalId: dto.ExternalId ?? string.Empty, name: dto.Name ?? string.Empty) + { + ExternalId = dto.ExternalId ?? string.Empty, + Links = links!, + ProjectId = new Guid(projectId ?? string.Empty), + Namespace = dto.Namespace ?? string.Empty, + Classname = dto.Classname ?? string.Empty, + Steps = ConvertStepsToModel(dto.Steps) ?? [], + Setup = ConvertStepsToModel(dto.Setup) ?? [], + Teardown = ConvertStepsToModel(dto.Teardown) ?? [], + Title = dto.Title ?? string.Empty, + Description = dto.Description ?? string.Empty, + Labels = ConvertLabelsToModel(dto.Labels) ?? [] + }; + } + + public static UpdateAutoTestRequest ConvertAutoTestDtoToPutModel(AutoTest dto, string? projectId) + { + var links = dto.Links?.Select(l => + new LinkPutModel( + title: l.Title, + url: l.Url, + description: l.Description, + type: Enum.Parse(l.Type.ToString()!)) + ).ToList(); + + + return new UpdateAutoTestRequest(externalId: dto.ExternalId ?? string.Empty, name: dto.Name ?? string.Empty) + { + Links = links ?? [], + ProjectId = new Guid(projectId ?? string.Empty), + Name = dto.Name ?? string.Empty, + Namespace = dto.Namespace ?? string.Empty, + Classname = dto.Classname ?? string.Empty, + Steps = ConvertStepsToModel(dto.Steps) ?? [], + Setup = ConvertStepsToModel(dto.Setup) ?? [], + Teardown = ConvertStepsToModel(dto.Teardown) ?? [], + Title = dto.Title ?? string.Empty, + Description = dto.Description ?? string.Empty, + Labels = ConvertLabelsToModel(dto.Labels) ?? [], + IsFlaky = dto.IsFlaky + }; + } + + public static AutoTestResultsForTestRunModel ConvertResultToModel(AutoTestResult dto, string? configurationId) + { + var links = dto.Links?.Select(l => + new LinkPostModel( + l.Title, + l.Url, + l.Description, + Enum.Parse(l.Type.ToString()!)) + ).ToList(); + + return new AutoTestResultsForTestRunModel( + autoTestExternalId: dto.ExternalId ?? string.Empty, + outcome: Enum.Parse(dto?.Outcome?.ToString() ?? string.Empty)) + { + ConfigurationId = new Guid(configurationId ?? string.Empty), + Links = links ?? [], + Message = dto?.Message ?? string.Empty, + Traces = dto?.Traces ?? string.Empty, + StartedOn = dto?.StartedOn, + CompletedOn = dto?.CompletedOn, + Duration = dto?.Duration, + Attachments = dto?.Attachments?.Select(a => new AttachmentPutModel(a)).ToList() ?? [], + Parameters = dto?.Parameters ?? [], + StepResults = ConvertResultStepToModel(dto?.StepResults), + SetupResults = ConvertResultStepToModel(dto?.SetupResults), + TeardownResults = ConvertResultStepToModel(dto?.TeardownResults) + }; + } + + private static List? ConvertLabelsToModel(IEnumerable? labels) + { + return labels?.Select(l => new LabelPostModel(l)).ToList(); + } + + private static List ConvertResultStepToModel( + IEnumerable? dtos) + { + return dtos?.Select(s => new AttachmentPutModelAutoTestStepResultsModel + { + Title = s.Title ?? string.Empty, + Description = s.Description ?? string.Empty, + StartedOn = s.StartedOn, + CompletedOn = s.CompletedOn, + Duration = s.Duration, + Attachments = s.Attachments?.Select(a => new AttachmentPutModel(a)).ToList() ?? [], + Parameters = s.Parameters ?? [], + StepResults = ConvertResultStepToModel(s?.Steps), + Outcome = Enum.Parse(s?.Outcome ?? string.Empty) + }).ToList() ?? []; + } + + private static List? ConvertStepsToModel(IEnumerable? stepDtos) + { + return stepDtos?.Select(s => new AutoTestStepModel( + s.Title ?? string.Empty, + s.Description ?? string.Empty, + ConvertStepsToModel(s.Steps) ?? [])).ToList(); + } +} \ No newline at end of file diff --git a/TmsRunner/Utils/LogParser.cs b/TmsRunner/Utils/LogParser.cs index caffc97..10ac126 100644 --- a/TmsRunner/Utils/LogParser.cs +++ b/TmsRunner/Utils/LogParser.cs @@ -1,6 +1,6 @@ +using Microsoft.VisualStudio.TestPlatform.ObjectModel; using System.Text.Json; using System.Text.RegularExpressions; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Tms.Adapter.Attributes; using Tms.Adapter.Models; using Tms.Adapter.Utils; @@ -9,17 +9,8 @@ namespace TmsRunner.Utils; -public class LogParser +public sealed class LogParser(Replacer replacer, Reflector reflector) { - private readonly Replacer _replacer; - private readonly Reflector _reflector; - - public LogParser(Replacer replacer, Reflector reflector) - { - _replacer = replacer; - _reflector = reflector; - } - public Dictionary? GetParameters(string traceJson) { var pattern = $"{MessageType.TmsParameters}:\\s*([^\\n\\r]*)"; @@ -30,7 +21,7 @@ public LogParser(Replacer replacer, Reflector reflector) return string.IsNullOrEmpty(json) ? null : JsonSerializer.Deserialize>(json); } - public string GetMessage(string traceJson) + public static string GetMessage(string traceJson) { var pattern = $"{MessageType.TmsStepMessage}:\\s*([^\\n\\r]*)"; var messageRegex = new Regex(pattern); @@ -40,7 +31,7 @@ public string GetMessage(string traceJson) return message; } - public List? GetLinks(string traceJson) + public static List? GetLinks(string traceJson) { var pattern = $"{MessageType.TmsStepLinks}:\\s*\\[([^\\n\\r]*)\\]"; var regex = new Regex(pattern); @@ -52,7 +43,7 @@ public string GetMessage(string traceJson) } var linksJsonArray = $"[ {string.Join(", ", matches.Select(m => m.Groups[1].Value))} ]"; - + return JsonSerializer.Deserialize>(linksJsonArray); } @@ -60,7 +51,7 @@ public string GetMessage(string traceJson) public AutoTest GetAutoTest(TestResult testResult, Dictionary? parameters) { var methodFullName = GetFullyQualifiedMethodName(testResult.TestCase.FullyQualifiedName); - var method = _reflector.GetMethodMetadata(testResult.TestCase.Source, methodFullName, parameters); + var method = Reflector.GetMethodMetadata(testResult.TestCase.Source, methodFullName, parameters); var autoTest = new AutoTest { @@ -69,50 +60,52 @@ public AutoTest GetAutoTest(TestResult testResult, Dictionary? p MethodName = method.Name }; - foreach (var attribute in method.Attributes) + var attributes = method.Attributes ?? []; + + foreach (var attribute in attributes) { switch (attribute) { case ExternalIdAttribute externalId: - autoTest.ExternalId = _replacer.ReplaceParameters(externalId.Value, parameters); + autoTest.ExternalId = replacer.ReplaceParameters(externalId.Value, parameters); break; case DisplayNameAttribute displayName: - autoTest.Name = _replacer.ReplaceParameters(displayName.Value, parameters); + autoTest.Name = replacer.ReplaceParameters(displayName.Value, parameters); break; case TitleAttribute title: - autoTest.Title = _replacer.ReplaceParameters(title.Value, parameters); + autoTest.Title = replacer.ReplaceParameters(title.Value, parameters); break; case DescriptionAttribute description: - autoTest.Description = _replacer.ReplaceParameters(description.Value, parameters); + autoTest.Description = replacer.ReplaceParameters(description.Value, parameters); break; case WorkItemIdsAttribute ids: - { - var workItemIds = ids.Value - .Select(id => _replacer.ReplaceParameters(id, parameters)) - .ToList(); + { + var workItemIds = ids.Value? + .Select(id => replacer.ReplaceParameters(id, parameters)) + .ToList(); - autoTest.WorkItemIds = workItemIds; - break; - } + autoTest.WorkItemIds = workItemIds ?? []; + break; + } case LinksAttribute links: - { - if (links.Value is not null) { - links.Value.Title = _replacer.ReplaceParameters(links.Value.Title, parameters); - links.Value.Url = _replacer.ReplaceParameters(links.Value.Url, parameters); - links.Value.Description = - _replacer.ReplaceParameters(links.Value.Description, parameters); + if (links.Value is not null) + { + links.Value.Title = replacer.ReplaceParameters(links.Value.Title, parameters); + links.Value.Url = replacer.ReplaceParameters(links.Value.Url, parameters); + links.Value.Description = + replacer.ReplaceParameters(links.Value.Description, parameters); - autoTest.Links.Add(links.Value); - } + autoTest.Links?.Add(links.Value); + } - break; - } + break; + } case LabelsAttribute labels: - { - autoTest.Labels = labels.Value; - break; - } + { + autoTest.Labels = labels.Value; + break; + } } } diff --git a/TmsRunner/Utils/Reflector.cs b/TmsRunner/Utils/Reflector.cs index d9b9762..0865764 100644 --- a/TmsRunner/Utils/Reflector.cs +++ b/TmsRunner/Utils/Reflector.cs @@ -3,32 +3,25 @@ namespace TmsRunner.Utils; -public class Reflector +public sealed class Reflector { - public MethodMetadata GetMethodMetadata(string assemblyPath, string methodName, + public static MethodMetadata GetMethodMetadata(string assemblyPath, string methodName, Dictionary? parameters) { var assembly = Assembly.LoadFrom(assemblyPath); var fullyQualifiedNameArray = methodName.Split("."); var type = assembly.GetType(string.Join(".", fullyQualifiedNameArray[..^1])); - var methods = type.GetMethods() + var methods = type?.GetMethods() .Where(m => m.Name.Equals(fullyQualifiedNameArray[^1])).ToList(); if (parameters is not null) { - methods = methods + methods = methods? .Where(m => CompareParameters(parameters, m.GetParameters())) .ToList(); } - var method = methods.FirstOrDefault(); - - if (method is null) - { - throw new ApplicationException( - $"Method {fullyQualifiedNameArray[^1]} not found!"); - } - + var method = (methods?.FirstOrDefault()) ?? throw new ApplicationException($"Method {fullyQualifiedNameArray[^1]} not found!"); var attributes = method.GetCustomAttributes(false) .Select(a => (Attribute)a) .ToList(); @@ -46,7 +39,9 @@ private static bool CompareParameters(Dictionary parameters, IReadOnlyList methodParameters) { if (methodParameters.Count != parameters.Count) + { return false; + } var i = 0; foreach (var parameter in parameters) diff --git a/TmsRunnerTests/TmsRunnerTests.csproj b/TmsRunnerTests/TmsRunnerTests.csproj index d491fce..9629550 100644 --- a/TmsRunnerTests/TmsRunnerTests.csproj +++ b/TmsRunnerTests/TmsRunnerTests.csproj @@ -12,7 +12,7 @@ - + diff --git a/TmsRunnerTests/Utils/LogParserTests.cs b/TmsRunnerTests/Utils/LogParserTests.cs index 0d5cf08..4f3be49 100644 --- a/TmsRunnerTests/Utils/LogParserTests.cs +++ b/TmsRunnerTests/Utils/LogParserTests.cs @@ -70,7 +70,7 @@ public void GetMessage_TraceIsEmpty() { var parser = new LogParser(_replacer, _reflector); - var result = parser.GetMessage(string.Empty); + var result = LogParser.GetMessage(string.Empty); Assert.AreEqual(string.Empty, result); } @@ -80,7 +80,7 @@ public void GetMessage_TraceWithMessage() { var parser = new LogParser(_replacer, _reflector); - var result = parser.GetMessage(message); + var result = LogParser.GetMessage(message); Assert.AreEqual(MessageValue, result); } @@ -91,7 +91,7 @@ public void GetMessage_TraceWithoutMessage() var parser = new LogParser(_replacer, _reflector); const string trace = "some trace"; - var result = parser.GetMessage(trace); + var result = LogParser.GetMessage(trace); Assert.AreEqual(string.Empty, result); } @@ -101,7 +101,7 @@ public void GetLinks_TraceIsEmpty() { var parser = new LogParser(_replacer, _reflector); - var result = parser.GetLinks(string.Empty); + var result = LogParser.GetLinks(string.Empty); Assert.IsNull(result); } @@ -126,7 +126,7 @@ public void GetLinks_TraceWithLinks() Type = LinkType.Defect }; - var result = parser.GetLinks(message); + var result = LogParser.GetLinks(message); Assert.IsNotNull(result); Assert.AreEqual(ValueCount, result.Count); @@ -140,7 +140,7 @@ public void GetLinks_TraceWithoutLinks() var parser = new LogParser(_replacer, _reflector); const string trace = "some trace"; - var result = parser.GetLinks(trace); + var result = LogParser.GetLinks(trace); Assert.IsNull(result); }