diff --git a/src/Testura.Mutation.Application/Commands/Project/OpenProject/OpenProjectCommandHandler.cs b/src/Testura.Mutation.Application/Commands/Project/OpenProject/OpenProjectCommandHandler.cs index f343eaf..73555ad 100644 --- a/src/Testura.Mutation.Application/Commands/Project/OpenProject/OpenProjectCommandHandler.cs +++ b/src/Testura.Mutation.Application/Commands/Project/OpenProject/OpenProjectCommandHandler.cs @@ -8,65 +8,48 @@ using Testura.Mutation.Application.Commands.Project.OpenProject.Handlers; using Testura.Mutation.Application.Exceptions; using Testura.Mutation.Application.Models; -using Testura.Mutation.Core.Baseline; using Testura.Mutation.Core.Config; -using Testura.Mutation.Core.Creator.Filter; -using Testura.Mutation.Core.Git; -using Testura.Mutation.Core.Solution; namespace Testura.Mutation.Application.Commands.Project.OpenProject { public class OpenProjectCommandHandler : IRequestHandler { - private readonly BaselineCreator _baselineCreator; - private readonly IGitCloner _gitCloner; - private readonly MutationDocumentFilterItemGitDiffCreator _diffCreator; - private readonly ISolutionBuilder _solutionBuilder; - private readonly ISolutionOpener _solutionOpener; + private readonly OpenProjectHandler _handler; public OpenProjectCommandHandler( - BaselineCreator baselineCreator, - IGitCloner gitCloner, - MutationDocumentFilterItemGitDiffCreator diffCreator, - ISolutionBuilder solutionBuilder, - ISolutionOpener solutionOpener) + OpenProjectExistHandler openProjectExistHandler, + OpenProjectMutatorsHandler openProjectMutatorsHandler, + OpenProjectGitFilterHandler openProjectGitFilterHandler, + OpenProjectWorkspaceHandler openProjectWorkspaceHandler) { - _baselineCreator = baselineCreator; - _gitCloner = gitCloner; - _diffCreator = diffCreator; - _solutionBuilder = solutionBuilder; - _solutionOpener = solutionOpener; + _handler = openProjectExistHandler; + + _handler + .SetNext(openProjectMutatorsHandler) + .SetNext(openProjectGitFilterHandler) + .SetNext(openProjectWorkspaceHandler); } public async Task Handle(OpenProjectCommand command, CancellationToken cancellationToken) { var path = command.Path; + MutationConfig applicationConfig = null; + MutationFileConfig fileConfig = null; LogTo.Info($"Opening project at {command.Config?.SolutionPath ?? path}"); try { - var (fileConfig, applicationConfig) = LoadConfigs(path, command.Config); - - var handler = new OpenProjectExistHandler(_gitCloner); + (fileConfig, applicationConfig) = LoadConfigs(path, command.Config); - handler - .SetNext(new OpenProjectMutatorsHandler()) - .SetNext(new OpenProjectGitFilterHandler(_diffCreator)) - .SetNext(new OpenProjectWorkspaceHandler(_baselineCreator, _solutionOpener)); - - try - { - await handler.HandleAsync(fileConfig, applicationConfig, cancellationToken); - } - catch (OperationCanceledException) - { - LogTo.Info("Open project was cancelled by request"); - return applicationConfig; - } + await _handler.HandleAsync(fileConfig, applicationConfig, cancellationToken); LogTo.Info("Opening project finished."); - + return applicationConfig; + } + catch (OperationCanceledException) + { + LogTo.Info("Open project was cancelled by request"); return applicationConfig; } catch (Exception ex) diff --git a/src/Testura.Mutation.Console/Bootstrapper.cs b/src/Testura.Mutation.Console/Bootstrapper.cs index 9fe64b3..50b8afd 100644 --- a/src/Testura.Mutation.Console/Bootstrapper.cs +++ b/src/Testura.Mutation.Console/Bootstrapper.cs @@ -4,6 +4,7 @@ using Testura.Mutation.Core.Execution.Runners; using Testura.Mutation.Core.Git; using Testura.Mutation.Core.Solution; +using Testura.Mutation.Core.Util.FileSystem; using Testura.Mutation.Infrastructure; using Testura.Mutation.Infrastructure.Git; using Testura.Mutation.Infrastructure.Solution; @@ -26,6 +27,7 @@ public static IUnityContainer GetContainer() unityContainer.RegisterType(); unityContainer.RegisterType(new ContainerControlledLifetimeManager()); unityContainer.RegisterType(); + unityContainer.RegisterType(); return unityContainer; } } diff --git a/src/Testura.Mutation.Core/Baseline/BaselineCreator.cs b/src/Testura.Mutation.Core/Baseline/BaselineCreator.cs index 52e1ff3..80f97f0 100644 --- a/src/Testura.Mutation.Core/Baseline/BaselineCreator.cs +++ b/src/Testura.Mutation.Core/Baseline/BaselineCreator.cs @@ -1,40 +1,33 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Anotar.Log4Net; -using ConsoleTables; -using Newtonsoft.Json.Linq; +using Testura.Mutation.Core.Baseline.Handlers; using Testura.Mutation.Core.Config; -using Testura.Mutation.Core.Exceptions; -using Testura.Mutation.Core.Execution; -using Testura.Mutation.Core.Execution.Compilation; -using Testura.Mutation.Core.Execution.Result; -using Testura.Mutation.Core.Execution.Runners; -using Testura.Mutation.Core.Solution; +using Testura.Mutation.Core.Util.FileSystem; namespace Testura.Mutation.Core.Baseline { public class BaselineCreator { - private readonly IProjectCompiler _projectCompiler; - private readonly TestRunnerDependencyFilesHandler _testRunnerDependencyFilesHandler; - private readonly ISolutionOpener _solutionOpener; - private ITestRunnerClient _testRunnerClient; + private readonly IDirectoryHandler _directoryHandler; + private readonly BaselineCreatorHandler _handler; public BaselineCreator( - IProjectCompiler projectCompiler, - ITestRunnerClient testRunnerClient, - ISolutionOpener solutionOpener, - TestRunnerDependencyFilesHandler testRunnerDependencyFilesHandler) + IDirectoryHandler directoryHandler, + BaselineCreatorCompileMutationProjectsHandler baselineCreatorCompileMutationProjectsHandler, + BaselineCreatorRunUnitTestsHandler baselineCreatorRunUnitTestsHandler, + BaselineCreatorLogSummaryHandler baselineCreatorLogSummaryHandler) { - _projectCompiler = projectCompiler; - _testRunnerClient = testRunnerClient; - _solutionOpener = solutionOpener; - _testRunnerDependencyFilesHandler = testRunnerDependencyFilesHandler; + _directoryHandler = directoryHandler; + _handler = baselineCreatorCompileMutationProjectsHandler; + + _handler + .SetNext(baselineCreatorRunUnitTestsHandler) + .SetNext(baselineCreatorLogSummaryHandler); } private string BaselineDirectoryPath => Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestRun", "Baseline"); @@ -42,60 +35,12 @@ public BaselineCreator( public async Task> CreateBaselineAsync(MutationConfig config, CancellationToken cancellationToken = default(CancellationToken)) { LogTo.Info("Creating baseline and verifying solution/tests.."); - - DeleteBaselineDirectory(); + _directoryHandler.DeleteDirectory(BaselineDirectoryPath); try { - cancellationToken.ThrowIfCancellationRequested(); - - Directory.CreateDirectory(BaselineDirectoryPath); - - foreach (var mutationProject in config.MutationProjects) - { - var project = config.Solution.Projects.FirstOrDefault(p => p.Name == mutationProject.Project.Name); - var result = await _projectCompiler.CompileAsync(BaselineDirectoryPath, project); - - if (!result.IsSuccess) - { - foreach (var compilationError in result.Errors) - { - LogTo.Error($"{{ Error = \"{compilationError.Message}\", Location = \"{compilationError.Location}\""); - } - - throw new BaselineException( - $"Failed to compile {project.Name} in base line.", - new CompilationException(result.Errors.Select(e => e.Message))); - } - } - var baselineInfos = new List(); - - foreach (var testProject in config.TestProjects) - { - cancellationToken.ThrowIfCancellationRequested(); - - var result = await RunTestAsync(testProject, config.DotNetPath, config.MaxTestTimeMin, cancellationToken); - - if (!result.IsSuccess) - { - var failedTests = result.TestResults.Where(t => !t.IsSuccess); - LogTo.Error("Unit tests failed with base line"); - LogTo.Error($"Name: {result.Name}"); - - foreach (var failedTest in failedTests) - { - LogTo.Error(JObject.FromObject(new { TestName = failedTest.Name, Message = failedTest.InnerText }).ToString()); - } - - throw new BaselineException("Failed to run all unit tests with baseline which make mutation testing impossible. See log for more details."); - } - - LogTo.Info($"..done ({result.TestResults.Count(t => t.IsSuccess)} tests passed)."); - baselineInfos.Add(new BaselineInfo(testProject.Project.Name, result.ExecutionTime)); - } - - LogBaselineSummary(baselineInfos); + await _handler.HandleAsync(config, BaselineDirectoryPath, baselineInfos, cancellationToken); LogTo.Info("Baseline completed."); return baselineInfos; @@ -107,55 +52,7 @@ public BaselineCreator( } finally { - DeleteBaselineDirectory(); - } - } - - private async Task RunTestAsync( - TestProject testProject, - string dotNetPath, - int maxTestTimeMin, - CancellationToken cancellationToken = default(CancellationToken)) - { - LogTo.Info($"Starting to run tests in {testProject.Project.OutputFileName}"); - var testDirectoryPath = Path.Combine(BaselineDirectoryPath, Guid.NewGuid().ToString()); - var testDllPath = Path.Combine(testDirectoryPath, testProject.Project.OutputFileName); - Directory.CreateDirectory(testDirectoryPath); - - // Copy all files from the test directory to our own mutation test directory - _testRunnerDependencyFilesHandler.CopyDependencies(testProject.Project.OutputDirectoryPath, testDirectoryPath); - - foreach (var file in Directory.EnumerateFiles(BaselineDirectoryPath)) - { - File.Copy(file, Path.Combine(testDirectoryPath, Path.GetFileName(file)), true); - } - - return await _testRunnerClient.RunTestsAsync(testProject.TestRunner, testDllPath, dotNetPath, TimeSpan.FromMinutes(maxTestTimeMin), cancellationToken); - } - - private void LogBaselineSummary(IList baselineInfos) - { - var table = new ConsoleTable("Project", "Execution time"); - foreach (var configBaselineInfo in baselineInfos) - { - table.AddRow(configBaselineInfo.TestProjectName, configBaselineInfo.ExecutionTime); - } - - LogTo.Info($"\n{table.ToStringAlternative()}"); - } - - private void DeleteBaselineDirectory() - { - try - { - if (Directory.Exists(BaselineDirectoryPath)) - { - Directory.Delete(BaselineDirectoryPath, true); - } - } - catch (Exception ex) - { - LogTo.Error($"Failed to delete baseline directory: {ex.Message}"); + _directoryHandler.DeleteDirectory(BaselineDirectoryPath); } } } diff --git a/src/Testura.Mutation.Core/Baseline/Handlers/BaselineCreatorCompileMutationProjectsHandler.cs b/src/Testura.Mutation.Core/Baseline/Handlers/BaselineCreatorCompileMutationProjectsHandler.cs new file mode 100644 index 0000000..cebf8a7 --- /dev/null +++ b/src/Testura.Mutation.Core/Baseline/Handlers/BaselineCreatorCompileMutationProjectsHandler.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Anotar.Log4Net; +using Testura.Mutation.Core.Config; +using Testura.Mutation.Core.Exceptions; +using Testura.Mutation.Core.Execution.Compilation; +using Testura.Mutation.Core.Util.FileSystem; + +namespace Testura.Mutation.Core.Baseline.Handlers +{ + public class BaselineCreatorCompileMutationProjectsHandler : BaselineCreatorHandler + { + private readonly IProjectCompiler _projectCompiler; + private readonly IDirectoryHandler _directoryHandler; + + public BaselineCreatorCompileMutationProjectsHandler(IProjectCompiler projectCompiler, IDirectoryHandler directoryHandler) + { + _projectCompiler = projectCompiler; + _directoryHandler = directoryHandler; + } + + public override async Task HandleAsync(MutationConfig config, string baselineDirectoryPath, IList baselineInfos, CancellationToken cancellationToken = default(CancellationToken)) + { + _directoryHandler.CreateDirectory(baselineDirectoryPath); + + foreach (var mutationProject in config.MutationProjects) + { + LogTo.Info($"Compiling {mutationProject.Project.Name}.."); + + var project = config.Solution.Projects.FirstOrDefault(p => p.Name == mutationProject.Project.Name); + var result = await _projectCompiler.CompileAsync(baselineDirectoryPath, project); + + if (!result.IsSuccess) + { + foreach (var compilationError in result.Errors) + { + LogTo.Error($"{{ Error = \"{compilationError.Message}\", Location = \"{compilationError.Location}\""); + } + + throw new BaselineException( + $"Failed to compile {project.Name} in base line.", + new CompilationException(result.Errors.Select(e => e.Message))); + } + } + + await base.HandleAsync(config, baselineDirectoryPath, baselineInfos, cancellationToken); + } + } +} diff --git a/src/Testura.Mutation.Core/Baseline/Handlers/BaselineCreatorHandler.cs b/src/Testura.Mutation.Core/Baseline/Handlers/BaselineCreatorHandler.cs new file mode 100644 index 0000000..8eb5e60 --- /dev/null +++ b/src/Testura.Mutation.Core/Baseline/Handlers/BaselineCreatorHandler.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Testura.Mutation.Core.Config; + +namespace Testura.Mutation.Core.Baseline.Handlers +{ + public abstract class BaselineCreatorHandler + { + public BaselineCreatorHandler Next { get; set; } + + public virtual Task HandleAsync(MutationConfig config, string baselineDirectoryPath, IList baselineInfos, CancellationToken cancellationToken = default(CancellationToken)) + { + return Next?.HandleAsync(config, baselineDirectoryPath, baselineInfos, cancellationToken) ?? Task.CompletedTask; + } + + public BaselineCreatorHandler SetNext(BaselineCreatorHandler handler) + { + Next = handler; + return Next; + } + } +} diff --git a/src/Testura.Mutation.Core/Baseline/Handlers/BaselineCreatorLogSummaryHandler.cs b/src/Testura.Mutation.Core/Baseline/Handlers/BaselineCreatorLogSummaryHandler.cs new file mode 100644 index 0000000..e46db18 --- /dev/null +++ b/src/Testura.Mutation.Core/Baseline/Handlers/BaselineCreatorLogSummaryHandler.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Anotar.Log4Net; +using ConsoleTables; +using Testura.Mutation.Core.Config; + +namespace Testura.Mutation.Core.Baseline.Handlers +{ + public class BaselineCreatorLogSummaryHandler : BaselineCreatorHandler + { + public override Task HandleAsync(MutationConfig config, string baselineDirectoryPath, IList baselineInfos, CancellationToken cancellationToken = default(CancellationToken)) + { + var table = new ConsoleTable("Project", "Execution time"); + foreach (var configBaselineInfo in baselineInfos) + { + table.AddRow(configBaselineInfo.TestProjectName, configBaselineInfo.ExecutionTime); + } + + LogTo.Info($"\n{table.ToStringAlternative()}"); + + return base.HandleAsync(config, baselineDirectoryPath, baselineInfos, cancellationToken); + } + } +} diff --git a/src/Testura.Mutation.Core/Baseline/Handlers/BaselineCreatorRunUnittestsHandler.cs b/src/Testura.Mutation.Core/Baseline/Handlers/BaselineCreatorRunUnittestsHandler.cs new file mode 100644 index 0000000..36389eb --- /dev/null +++ b/src/Testura.Mutation.Core/Baseline/Handlers/BaselineCreatorRunUnittestsHandler.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Anotar.Log4Net; +using Newtonsoft.Json.Linq; +using Testura.Mutation.Core.Config; +using Testura.Mutation.Core.Exceptions; +using Testura.Mutation.Core.Execution; +using Testura.Mutation.Core.Execution.Result; +using Testura.Mutation.Core.Execution.Runners; +using Testura.Mutation.Core.Util.FileSystem; + +namespace Testura.Mutation.Core.Baseline.Handlers +{ + public class BaselineCreatorRunUnitTestsHandler : BaselineCreatorHandler + { + private readonly IDirectoryHandler _directoryHandler; + private readonly ITestRunnerClient _testRunnerClient; + private readonly TestRunnerDependencyFilesHandler _testRunnerDependencyFilesHandler; + + public BaselineCreatorRunUnitTestsHandler( + IDirectoryHandler directoryHandler, + ITestRunnerClient testRunnerClient, + TestRunnerDependencyFilesHandler testRunnerDependencyFilesHandler) + { + _directoryHandler = directoryHandler; + _testRunnerClient = testRunnerClient; + _testRunnerDependencyFilesHandler = testRunnerDependencyFilesHandler; + } + + public override async Task HandleAsync(MutationConfig config, string baselineDirectoryPath, IList baselineInfos, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + foreach (var testProject in config.TestProjects) + { + cancellationToken.ThrowIfCancellationRequested(); + + var result = await RunTestAsync(testProject, config.DotNetPath, config.MaxTestTimeMin, baselineDirectoryPath, cancellationToken); + + if (!result.IsSuccess) + { + var failedTests = result.TestResults.Where(t => !t.IsSuccess); + LogTo.Error("Unit tests failed with base line"); + LogTo.Error($"Name: {result.Name}"); + + foreach (var failedTest in failedTests) + { + LogTo.Error(JObject.FromObject(new { TestName = failedTest.Name, Message = failedTest.InnerText }).ToString()); + } + + throw new BaselineException("Failed to run all unit tests with baseline which make mutation testing impossible. See log for more details."); + } + + LogTo.Info($"..done ({result.TestResults.Count(t => t.IsSuccess)} tests passed)."); + baselineInfos.Add(new BaselineInfo(testProject.Project.Name, result.ExecutionTime)); + } + + await base.HandleAsync(config, baselineDirectoryPath, baselineInfos, cancellationToken); + } + + private async Task RunTestAsync( + TestProject testProject, + string dotNetPath, + int maxTestTimeMin, + string baselineDirectoryPath, + CancellationToken cancellationToken = default(CancellationToken)) + { + LogTo.Info($"Starting to run tests in {testProject.Project.OutputFileName}"); + var testDirectoryPath = Path.Combine(baselineDirectoryPath, Guid.NewGuid().ToString()); + var testDllPath = Path.Combine(testDirectoryPath, testProject.Project.OutputFileName); + + _directoryHandler.CreateDirectory(testDirectoryPath); + + // Copy all files from the test directory to our own mutation test directory + _testRunnerDependencyFilesHandler.CopyDependencies(testProject.Project.OutputDirectoryPath, testDirectoryPath); + + foreach (var file in Directory.EnumerateFiles(baselineDirectoryPath)) + { + File.Copy(file, Path.Combine(testDirectoryPath, Path.GetFileName(file)), true); + } + + return await _testRunnerClient.RunTestsAsync(testProject.TestRunner, testDllPath, dotNetPath, TimeSpan.FromMinutes(maxTestTimeMin), cancellationToken); + } + } +} diff --git a/src/Testura.Mutation.Core/Testura.Mutation.Core.csproj b/src/Testura.Mutation.Core/Testura.Mutation.Core.csproj index 0e96e21..d727445 100644 --- a/src/Testura.Mutation.Core/Testura.Mutation.Core.csproj +++ b/src/Testura.Mutation.Core/Testura.Mutation.Core.csproj @@ -49,6 +49,10 @@ + + + + @@ -108,6 +112,8 @@ + + diff --git a/src/Testura.Mutation.Core/Util/FileSystem/DirectoryHandler.cs b/src/Testura.Mutation.Core/Util/FileSystem/DirectoryHandler.cs new file mode 100644 index 0000000..6d36f7b --- /dev/null +++ b/src/Testura.Mutation.Core/Util/FileSystem/DirectoryHandler.cs @@ -0,0 +1,29 @@ +using System; +using System.IO; +using Anotar.Log4Net; + +namespace Testura.Mutation.Core.Util.FileSystem +{ + public class DirectoryHandler : IDirectoryHandler + { + public void CreateDirectory(string path) + { + Directory.CreateDirectory(path); + } + + public void DeleteDirectory(string path) + { + try + { + if (Directory.Exists(path)) + { + Directory.Delete(path, true); + } + } + catch (Exception ex) + { + LogTo.Error($"Failed to delete baseline directory: {ex.Message}"); + } + } + } +} diff --git a/src/Testura.Mutation.Core/Util/FileSystem/IDirectoryHandler.cs b/src/Testura.Mutation.Core/Util/FileSystem/IDirectoryHandler.cs new file mode 100644 index 0000000..32a1668 --- /dev/null +++ b/src/Testura.Mutation.Core/Util/FileSystem/IDirectoryHandler.cs @@ -0,0 +1,9 @@ +namespace Testura.Mutation.Core.Util.FileSystem +{ + public interface IDirectoryHandler + { + void CreateDirectory(string path); + + void DeleteDirectory(string path); + } +} diff --git a/src/Testura.Mutation.VsExtension/Bootstrapper.cs b/src/Testura.Mutation.VsExtension/Bootstrapper.cs index 4d5275f..f1a7d0e 100644 --- a/src/Testura.Mutation.VsExtension/Bootstrapper.cs +++ b/src/Testura.Mutation.VsExtension/Bootstrapper.cs @@ -8,6 +8,7 @@ using Testura.Mutation.Core.Execution.Runners; using Testura.Mutation.Core.Git; using Testura.Mutation.Core.Solution; +using Testura.Mutation.Core.Util.FileSystem; using Testura.Mutation.Infrastructure; using Testura.Mutation.Infrastructure.Git; using Testura.Mutation.TestRunner; @@ -62,6 +63,7 @@ protected override void ConfigureContainer() Container.RegisterType(); Container.RegisterType(); Container.RegisterType(); + Container.RegisterType(); base.ConfigureContainer(); }