You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
211 lines
6.7 KiB
211 lines
6.7 KiB
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) |
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) |
|
|
|
using System; |
|
using System.Collections.Generic; |
|
using System.Diagnostics; |
|
using System.Linq; |
|
using System.Threading; |
|
using System.Threading.Tasks; |
|
|
|
using ICSharpCode.Core; |
|
using ICSharpCode.NRefactory.Utils; |
|
using ICSharpCode.SharpDevelop; |
|
using ICSharpCode.SharpDevelop.Gui; |
|
using ICSharpCode.SharpDevelop.Project; |
|
using ICSharpCode.SharpDevelop.Workbench; |
|
|
|
namespace ICSharpCode.UnitTesting.Frameworks |
|
{ |
|
/// <summary> |
|
/// Manages the execution of tests across multiple projects. |
|
/// Takes care of building the projects (if necessary) and showing progress in the UI. |
|
/// </summary> |
|
public class TestExecutionManager |
|
{ |
|
readonly IBuildService buildService; |
|
readonly IUnitTestTaskService taskService; |
|
readonly IUnitTestSaveAllFilesCommand saveAllFilesCommand; |
|
readonly ITestService testService; |
|
readonly IWorkbench workbench; |
|
readonly IMessageLoop mainThread; |
|
readonly IStatusBarService statusBarService; |
|
readonly IBuildOptions buildOptions; |
|
|
|
public TestExecutionManager() |
|
{ |
|
this.buildService = SD.BuildService; |
|
this.taskService = new UnitTestTaskService(); |
|
this.saveAllFilesCommand = new UnitTestSaveAllFilesCommand(); |
|
this.testService = SD.GetRequiredService<ITestService>(); |
|
this.workbench = SD.Workbench; |
|
this.statusBarService = SD.StatusBar; |
|
this.mainThread = SD.MainThread; |
|
this.buildOptions = new UnitTestBuildOptions(); |
|
} |
|
|
|
readonly MultiDictionary<ITestProject, ITest> testsByProject = new MultiDictionary<ITestProject, ITest>(); |
|
CancellationToken cancellationToken; |
|
ITestProject currentProjectBeingTested; |
|
IProgressMonitor testProgressMonitor; |
|
|
|
public async Task RunTestsAsync(IEnumerable<ITest> selectedTests, TestExecutionOptions options, CancellationToken cancellationToken) |
|
{ |
|
this.cancellationToken = cancellationToken; |
|
GroupTestsByProject(selectedTests); |
|
|
|
ClearTasks(); |
|
ShowUnitTestsPad(); |
|
ShowOutputPad(); |
|
|
|
ResetTestResults(); |
|
saveAllFilesCommand.SaveAllFiles(); |
|
|
|
// Run the build, if necessary: |
|
var projectsToBuild = testsByProject.Keys.Where(p => p.IsBuildNeededBeforeTestRun).Select(p => p.Project).ToList(); |
|
if (projectsToBuild.Count > 0) { |
|
using (cancellationToken.Register(buildService.CancelBuild)) { |
|
var buildOptions = new BuildOptions(BuildTarget.Build); |
|
buildOptions.BuildDetection = BuildOptions.BuildOnExecute; |
|
var buildResults = await buildService.BuildAsync(projectsToBuild, buildOptions); |
|
if (buildResults.Result != BuildResultCode.Success) |
|
return; |
|
} |
|
} |
|
|
|
cancellationToken.ThrowIfCancellationRequested(); |
|
using (IProgressMonitor progressMonitor = statusBarService.CreateProgressMonitor(cancellationToken)) { |
|
int projectsLeftToRun = testsByProject.Count; |
|
foreach (IGrouping<ITestProject, ITest> g in testsByProject.OrderBy(g => g.Key.DisplayName)) { |
|
currentProjectBeingTested = g.Key; |
|
progressMonitor.TaskName = GetProgressMonitorLabel(currentProjectBeingTested); |
|
progressMonitor.Progress = GetProgress(projectsLeftToRun); |
|
using (testProgressMonitor = progressMonitor.CreateSubTask(1.0 / testsByProject.Count)) { |
|
using (ITestRunner testRunner = currentProjectBeingTested.CreateTestRunner(options)) { |
|
testRunner.TestFinished += testRunner_TestFinished; |
|
var writer = new MessageViewCategoryTextWriter(testService.UnitTestMessageView); |
|
await testRunner.RunAsync(g, testProgressMonitor, writer, testProgressMonitor.CancellationToken); |
|
} |
|
} |
|
projectsLeftToRun--; |
|
progressMonitor.CancellationToken.ThrowIfCancellationRequested(); |
|
} |
|
} |
|
|
|
ShowErrorList(); |
|
} |
|
|
|
void GroupTestsByProject(IEnumerable<ITest> selectedTests) |
|
{ |
|
foreach (ITest test in selectedTests) { |
|
if (test == null) |
|
continue; |
|
if (test.ParentProject == null) { |
|
// When a solution is selected, select all its projects individually |
|
foreach (ITest project in test.NestedTests) { |
|
Debug.Assert(project == project.ParentProject); |
|
testsByProject.Add(project.ParentProject, project); |
|
} |
|
} else { |
|
testsByProject.Add(test.ParentProject, test); |
|
} |
|
cancellationToken.ThrowIfCancellationRequested(); |
|
} |
|
} |
|
|
|
void ClearTasks() |
|
{ |
|
taskService.BuildMessageViewCategory.Clear(); |
|
taskService.ClearExceptCommentTasks(); |
|
testService.UnitTestMessageView.Clear(); |
|
} |
|
|
|
void ShowUnitTestsPad() |
|
{ |
|
var descriptor = workbench.GetPad(typeof(UnitTestsPad)); |
|
descriptor.BringPadToFront(); |
|
var pad = descriptor.PadContent as UnitTestsPad; |
|
if (pad != null) { |
|
pad.TreeView.SelectedTests = testsByProject.Values; |
|
} |
|
} |
|
|
|
void ShowOutputPad() |
|
{ |
|
testService.UnitTestMessageView.Activate(true); |
|
} |
|
|
|
void ResetTestResults() |
|
{ |
|
cancellationToken.ThrowIfCancellationRequested(); |
|
foreach (ITest test in testsByProject.Values) { |
|
test.ResetTestResults(); |
|
} |
|
cancellationToken.ThrowIfCancellationRequested(); |
|
} |
|
|
|
string GetProgressMonitorLabel(ITestProject project) |
|
{ |
|
StringTagPair tagPair = new StringTagPair("Name", project.DisplayName); |
|
return StringParser.Parse("${res:ICSharpCode.UnitTesting.StatusBarProgressLabel}", tagPair); |
|
} |
|
|
|
double GetProgress(int projectsLeftToRunCount) |
|
{ |
|
int totalProjectCount = testsByProject.Count; |
|
return (double)(totalProjectCount - projectsLeftToRunCount) / totalProjectCount; |
|
} |
|
|
|
void testRunner_TestFinished(object sender, TestFinishedEventArgs e) |
|
{ |
|
mainThread.InvokeAsyncAndForget(delegate { |
|
ShowResult(e.Result); |
|
}); |
|
} |
|
|
|
protected void ShowResult(TestResult result) |
|
{ |
|
if (IsTestResultFailureOrIsIgnored(result)) { |
|
AddTaskForTestResult(result); |
|
UpdateProgressMonitorStatus(result); |
|
} |
|
UpdateTestResult(result); |
|
} |
|
|
|
bool IsTestResultFailureOrIsIgnored(TestResult result) |
|
{ |
|
return result.IsFailure || result.IsIgnored; |
|
} |
|
|
|
void AddTaskForTestResult(TestResult testResult) |
|
{ |
|
SDTask task = TestResultTask.Create(testResult, currentProjectBeingTested); |
|
taskService.Add(task); |
|
} |
|
|
|
void UpdateProgressMonitorStatus(TestResult result) |
|
{ |
|
if (testProgressMonitor != null) { |
|
if (result.IsFailure) { |
|
testProgressMonitor.Status = OperationStatus.Error; |
|
} else if (result.IsIgnored && testProgressMonitor.Status == OperationStatus.Normal) { |
|
testProgressMonitor.Status = OperationStatus.Warning; |
|
} |
|
} |
|
} |
|
|
|
void UpdateTestResult(TestResult result) |
|
{ |
|
if (currentProjectBeingTested != null) { |
|
currentProjectBeingTested.UpdateTestResult(result); |
|
} |
|
} |
|
|
|
void ShowErrorList() |
|
{ |
|
if (taskService.SomethingWentWrong && buildOptions.ShowErrorListAfterBuild) { |
|
workbench.GetPad(typeof(ErrorListPad)).BringPadToFront(); |
|
} |
|
} |
|
} |
|
}
|
|
|