From dff21e821f8d34d9055f1e59e01762201ca5f195 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 9 Sep 2012 19:26:30 +0200 Subject: [PATCH] [WIP] Major refactoring of the unit testing addin: A lot of functionality gets moved from the main portion of the AddIn into the framework-specific providers. We no longer deal with test classes and members, but instead use the new ITest interface. A test can be a whole project, namespace, class, or just a single method. This can be used to support features like the NUnit [TestCase] attribute, where a single method might result in multiple tests. --- .../Commands/AbstractRunTestCommand.cs | 398 ------------------ .../Commands/IRunTestCommandContext.cs | 25 -- .../UnitTesting/Commands/PadCommands.cs | 56 +++ .../Commands/RunAllTestsInPadCommand.cs | 26 -- .../Commands/RunProjectTestsInPadCommand.cs | 42 -- .../Commands/RunTestCommandContext.cs | 68 --- .../Commands/RunTestInPadCommand.cs | 26 -- .../Commands/RunTestWithDebuggerCommand.cs | 26 -- .../Commands/RunningTestsCondition.cs | 4 +- .../UnitTesting/Commands/TestableCondition.cs | 88 +--- .../UnitTesting/Commands/UnitTestCommands.cs | 70 ++- .../Frameworks/IRegisteredTestFrameworks.cs | 23 - .../UnitTesting/Frameworks/ITestFramework.cs | 14 +- .../Frameworks/ITestFrameworkFactory.cs | 12 - .../UnitTesting/Frameworks/ITestService.cs | 51 +++ .../Frameworks/RegisteredTestFrameworks.cs | 99 ----- .../UnitTesting/Frameworks/SDTestService.cs | 88 ++++ .../Frameworks/TestExecutionManager.cs | 149 +++++++ .../Frameworks/TestExecutionOptions.cs | 19 + .../Frameworks/TestFrameworkDescriptor.cs | 8 +- .../Frameworks/TestFrameworkDoozer.cs | 7 +- .../Frameworks/TestFrameworkFactory.cs | 23 - .../UnitTesting/Frameworks/TestService.cs | 57 --- .../Interfaces/IBuildProjectFactory.cs | 2 +- .../Interfaces/IUnitTestTaskService.cs | 1 - .../Interfaces/IUnitTestWorkbench.cs | 15 - .../Interfaces/UnitTestBuildProjectFactory.cs | 2 +- .../Interfaces/UnitTestTaskService.cs | 5 - .../Interfaces/UnitTestWorkbench.cs | 27 -- .../Analysis/UnitTesting/Model/ITest.cs | 55 +++ .../UnitTesting/Model/ITestProject.cs | 47 +++ .../UnitTesting/Model/ITestSolution.cs | 33 ++ .../Analysis/UnitTesting/Model/TestBase.cs | 143 +++++++ .../UnitTesting/Model/TestCollection.cs | 199 +++++++++ .../UnitTesting/Model/TestNamespace.cs | 46 ++ .../UnitTesting/Model/TestProjectBase.cs | 58 +++ .../Model/TestResultTypeChangedEventArgs.cs | 30 ++ .../UnitTesting/Model/TestSolution.cs | 80 ++-- .../NUnit/NUnitConsoleApplication.cs | 16 +- .../UnitTesting/NUnit/NUnitTestDebugger.cs | 4 +- .../UnitTesting/NUnit/NUnitTestFramework.cs | 39 +- .../UnitTesting/NUnit/NUnitTestProject.cs | 26 ++ .../UnitTesting/NUnit/NUnitTestRunner.cs | 4 +- .../UnitTesting/Pad/EmptyUnitTestsPad.cs | 53 --- .../Analysis/UnitTesting/Pad/IUnitTestsPad.cs | 18 - .../Analysis/UnitTesting/Pad/UnitTestsPad.cs | 236 +---------- .../UnitTesting/TestRunner/ITestRunner.cs | 3 +- .../UnitTesting/TestRunner/SelectedTests.cs | 91 ---- .../TestRunner/TestDebuggerBase.cs | 4 +- .../TestRunner/TestProcessRunnerBase.cs | 4 +- .../UnitTesting/TestRunner/TestResultTask.cs | 4 +- .../UnitTesting/TestRunner/TestRunnerBase.cs | 5 +- .../UnitTesting/TreeView/ClassUnitTestNode.cs | 2 +- .../UnitTesting/TreeView/ITestTreeView.cs | 20 +- .../TreeView/MemberUnitTestNode.cs | 2 +- .../TreeView/NamespaceUnitTestNode.cs | 8 +- .../TreeView/ProjectUnitTestNode.cs | 10 +- .../UnitTesting/TreeView/RootUnitTestNode.cs | 8 +- .../UnitTesting/TreeView/TestTreeView.cs | 104 ++--- .../UnitTesting/TreeView/UnitTestBaseNode.cs | 38 -- .../UnitTesting/TreeView/UnitTestNode.cs | 128 ++++++ .../Analysis/UnitTesting/UnitTesting.addin | 21 +- .../Analysis/UnitTesting/UnitTesting.csproj | 56 +-- .../Utils/MultiDictionary.cs | 20 + .../Project/ICSharpCode.SharpDevelop.csproj | 1 + .../Project/Src/Commands/BuildCommands.cs | 23 +- .../Src/Project}/MultipleProjectBuildable.cs | 4 +- .../Project/Src/Services/Tasks/TaskService.cs | 22 +- 68 files changed, 1447 insertions(+), 1649 deletions(-) delete mode 100644 src/AddIns/Analysis/UnitTesting/Commands/AbstractRunTestCommand.cs delete mode 100644 src/AddIns/Analysis/UnitTesting/Commands/IRunTestCommandContext.cs create mode 100644 src/AddIns/Analysis/UnitTesting/Commands/PadCommands.cs delete mode 100644 src/AddIns/Analysis/UnitTesting/Commands/RunAllTestsInPadCommand.cs delete mode 100644 src/AddIns/Analysis/UnitTesting/Commands/RunProjectTestsInPadCommand.cs delete mode 100644 src/AddIns/Analysis/UnitTesting/Commands/RunTestCommandContext.cs delete mode 100644 src/AddIns/Analysis/UnitTesting/Commands/RunTestInPadCommand.cs delete mode 100644 src/AddIns/Analysis/UnitTesting/Commands/RunTestWithDebuggerCommand.cs delete mode 100644 src/AddIns/Analysis/UnitTesting/Frameworks/IRegisteredTestFrameworks.cs delete mode 100644 src/AddIns/Analysis/UnitTesting/Frameworks/ITestFrameworkFactory.cs create mode 100644 src/AddIns/Analysis/UnitTesting/Frameworks/ITestService.cs delete mode 100644 src/AddIns/Analysis/UnitTesting/Frameworks/RegisteredTestFrameworks.cs create mode 100644 src/AddIns/Analysis/UnitTesting/Frameworks/SDTestService.cs create mode 100644 src/AddIns/Analysis/UnitTesting/Frameworks/TestExecutionManager.cs create mode 100644 src/AddIns/Analysis/UnitTesting/Frameworks/TestExecutionOptions.cs delete mode 100644 src/AddIns/Analysis/UnitTesting/Frameworks/TestFrameworkFactory.cs delete mode 100644 src/AddIns/Analysis/UnitTesting/Frameworks/TestService.cs delete mode 100644 src/AddIns/Analysis/UnitTesting/Interfaces/IUnitTestWorkbench.cs delete mode 100644 src/AddIns/Analysis/UnitTesting/Interfaces/UnitTestWorkbench.cs create mode 100644 src/AddIns/Analysis/UnitTesting/Model/ITest.cs create mode 100644 src/AddIns/Analysis/UnitTesting/Model/ITestProject.cs create mode 100644 src/AddIns/Analysis/UnitTesting/Model/ITestSolution.cs create mode 100644 src/AddIns/Analysis/UnitTesting/Model/TestBase.cs create mode 100644 src/AddIns/Analysis/UnitTesting/Model/TestCollection.cs create mode 100644 src/AddIns/Analysis/UnitTesting/Model/TestNamespace.cs create mode 100644 src/AddIns/Analysis/UnitTesting/Model/TestProjectBase.cs create mode 100644 src/AddIns/Analysis/UnitTesting/Model/TestResultTypeChangedEventArgs.cs create mode 100644 src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestProject.cs delete mode 100644 src/AddIns/Analysis/UnitTesting/Pad/EmptyUnitTestsPad.cs delete mode 100644 src/AddIns/Analysis/UnitTesting/Pad/IUnitTestsPad.cs delete mode 100644 src/AddIns/Analysis/UnitTesting/TestRunner/SelectedTests.cs delete mode 100644 src/AddIns/Analysis/UnitTesting/TreeView/UnitTestBaseNode.cs create mode 100644 src/AddIns/Analysis/UnitTesting/TreeView/UnitTestNode.cs rename src/{AddIns/Analysis/UnitTesting/Commands => Main/Base/Project/Src/Project}/MultipleProjectBuildable.cs (92%) diff --git a/src/AddIns/Analysis/UnitTesting/Commands/AbstractRunTestCommand.cs b/src/AddIns/Analysis/UnitTesting/Commands/AbstractRunTestCommand.cs deleted file mode 100644 index d78df247b0..0000000000 --- a/src/AddIns/Analysis/UnitTesting/Commands/AbstractRunTestCommand.cs +++ /dev/null @@ -1,398 +0,0 @@ -// 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.IO; -using System.Linq; -using ICSharpCode.Core; -using ICSharpCode.SharpDevelop; -using ICSharpCode.SharpDevelop.Gui; -using ICSharpCode.SharpDevelop.Project; -using ICSharpCode.SharpDevelop.Project.Commands; - -namespace ICSharpCode.UnitTesting -{ - public abstract class AbstractRunTestCommand : AbstractMenuCommand - { - static AbstractRunTestCommand runningTestCommand; - IUnitTestsPad unitTestsPad; - SelectedTests selectedTests; - IRunTestCommandContext context; - ITestRunner testRunner; - IProgressMonitor testProgressMonitor; - int totalProjectCount; - - public AbstractRunTestCommand() - : this(new RunTestCommandContext()) - { - } - - public AbstractRunTestCommand(IRunTestCommandContext context) - { - this.context = context; - } - - protected IRunTestCommandContext Context { - get { return context; } - } - - /// - /// Gets the running test command. - /// - public static AbstractRunTestCommand RunningTestCommand { - get { return runningTestCommand; } - set { runningTestCommand = value; } - } - - /// - /// Gets whether a test is currently running. - /// - public static bool IsRunningTest { - get { return runningTestCommand != null; } - } - - public override void Run() - { - GetUnitTestsPad(); - - selectedTests = GetSelectedTests(); - if (selectedTests.HasProjects) { - runningTestCommand = this; - BeforeRun(); - BuildAndRunTests(); - } - } - - SelectedTests GetSelectedTests() - { - return new SelectedTests(Owner, unitTestsPad.GetProjects()); - } - - void GetUnitTestsPad() - { - unitTestsPad = context.OpenUnitTestsPad; - if (unitTestsPad == null) { - unitTestsPad = CreateEmptyUnitTestsPad(); - } - } - - EmptyUnitTestsPad CreateEmptyUnitTestsPad() - { - return new EmptyUnitTestsPad(context.OpenSolution); - } - - /// - /// Sets the initial workbench state before starting - /// a test run. - /// - void BeforeRun() - { - ClearTasks(); - ClearUnitTestCategoryText(); - - ShowUnitTestsPad(); - ShowOutputPad(); - - UpdateUnitTestsPadToolbar(); - ResetAllTestResultsInUnitTestsPad(); - - OnBeforeBuild(); - } - - void BuildAndRunTests() - { - if (IsBuildNeededBeforeTestRun()) { - BuildProjectBeforeRunningTests(selectedTests); - } else { - context.SaveAllFilesCommand.SaveAllFiles(); - RunTests(selectedTests); - } - } - - bool IsBuildNeededBeforeTestRun() - { - return selectedTests.Projects.Any(p => context.RegisteredTestFrameworks.IsBuildNeededBeforeTestRunForProject(p)); - } - - void ClearTasks() - { - context.TaskService.BuildMessageViewCategory.ClearText(); - context.TaskService.InUpdate = true; - context.TaskService.ClearExceptCommentTasks(); - context.TaskService.InUpdate = false; - } - - void ClearUnitTestCategoryText() - { - context.UnitTestCategory.ClearText(); - } - - void ShowUnitTestsPad() - { - unitTestsPad.BringToFront(); - } - - void StopProgressMonitor() - { - if (testProgressMonitor != null) { - testProgressMonitor.Dispose(); - testProgressMonitor = null; - } - } - - void UpdateUnitTestsPadToolbar() - { - unitTestsPad.UpdateToolbar(); - } - - /// - /// Called before the build is started (even if no build needs to be performed). - /// If multiple projects are to be tested this is called only once. - /// - protected virtual void OnBeforeBuild() - { - } - - /// - /// Called before all tests are run (after the build has finished successfully). - /// If multiple projects are to be tested this is called only once. - /// - protected virtual void OnBeforeRunTests() - { - } - - /// - /// Runs the tests after building the project(s) under test. - /// - void BuildProjectBeforeRunningTests(SelectedTests selectedTests) - { - BuildProject build = CreateBuildProjectBeforeTestRun(selectedTests); - build.BuildComplete += delegate { - OnBuildComplete(build.LastBuildResults, selectedTests); - }; - build.Run(); - } - - BuildProject CreateBuildProjectBeforeTestRun(SelectedTests selectedTests) - { - IEnumerable projects = GetProjectsRequiringBuildBeforeTestRun(selectedTests); - return context.BuildProjectFactory.CreateBuildProjectBeforeTestRun(projects); - } - - IEnumerable GetProjectsRequiringBuildBeforeTestRun(SelectedTests selectedTests) - { - return selectedTests - .Projects - .Where(p => context.RegisteredTestFrameworks.IsBuildNeededBeforeTestRunForProject(p)); - } - - /// - /// Stops running the tests. - /// - public void Stop() - { - StopActiveTestRunner(); - - runningTestCommand = null; - UpdateUnitTestsPadToolbar(); - - OnStop(); - } - - void StopActiveTestRunner() - { - if (testRunner != null) { - testRunner.Stop(); - testRunner.Dispose(); - testRunner = null; - } - } - - /// - /// Called after all tests have been run even if there have - /// been errors. If multiple projects are to be tested this is called only once. - /// - protected virtual void OnAfterRunTests() - { - } - - /// - /// Called by derived classes when a single test run - /// is finished. - /// - protected void TestRunCompleted() - { - StopActiveTestRunner(); - selectedTests.RemoveFirstProject(); - if (selectedTests.HasProjects) { - RunTests(selectedTests); - } else { - StopProgressMonitor(); - runningTestCommand = null; - UpdateUnitTestsPadToolbar(); - ShowErrorList(); - OnAfterRunTests(); - } - } - - void TestFinished(object source, TestFinishedEventArgs e) - { - context.Workbench.SafeThreadAsyncCall(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) - { - TestProject project = GetTestProjectForProject(selectedTests.Project); - SDTask task = TestResultTask.Create(testResult, project); - context.TaskService.Add(task); - } - - /// - /// Called when the test run should be stopped. - /// - protected virtual void OnStop() - { - } - - void ShowOutputPad() - { - ShowPad(context.Workbench.GetPad(typeof(CompilerMessageView))); - } - - protected void ShowPad(PadDescriptor padDescriptor) - { - context.Workbench.SafeThreadAsyncCall(padDescriptor.BringPadToFront); - } - - void ShowErrorList() - { - if (HasErrorsThatShouldBeDisplayed()) { - ShowPad(context.Workbench.GetPad(typeof(ErrorListPad))); - } - } - - bool HasErrorsThatShouldBeDisplayed() - { - return context.TaskService.SomethingWentWrong && - context.BuildOptions.ShowErrorListAfterBuild; - } - - /// - /// Runs the test for the project after a successful build. - /// - void OnBuildComplete(BuildResults results, SelectedTests selectedTests) - { - if (BuildHasNoErrorsAndStillRunningTests(results)) { - RunTests(selectedTests); - } else { - if (IsRunningTest) { - Stop(); - } - ShowErrorList(); - } - } - - bool BuildHasNoErrorsAndStillRunningTests(BuildResults results) - { - return (results.ErrorCount == 0) && IsRunningTest; - } - - void RunTests(SelectedTests selectedTests) - { - if (testProgressMonitor == null) { - OnBeforeRunTests(); - testProgressMonitor = context.StatusBarService.CreateProgressMonitor(); - totalProjectCount = selectedTests.ProjectsCount; - } - testProgressMonitor.TaskName = GetProgressMonitorLabel(selectedTests.Project); - testProgressMonitor.Progress = GetProgress(selectedTests.ProjectsCount); - - testRunner = CreateTestRunner(selectedTests.Project); - if (testRunner != null) { - StartTestRunner(); - } - } - - void StartTestRunner() - { - testRunner.MessageReceived += TestRunnerMessageReceived; - testRunner.AllTestsFinished += AllTestsFinished; - testRunner.TestFinished += TestFinished; - testRunner.Start(selectedTests); - } - - string GetProgressMonitorLabel(IProject project) - { - StringTagPair tagPair = new StringTagPair("Name", project.Name); - return StringParser.Parse("${res:ICSharpCode.UnitTesting.StatusBarProgressLabel}", tagPair); - } - - double GetProgress(int projectsLeftToRunCount) - { - return (double)(totalProjectCount - projectsLeftToRunCount) / totalProjectCount; - } - - protected virtual ITestRunner CreateTestRunner(IProject project) - { - return null; - } - - protected virtual void TestRunnerMessageReceived(object source, MessageReceivedEventArgs e) - { - context.UnitTestCategory.AppendLine(e.Message); - } - - void AllTestsFinished(object source, EventArgs e) - { - context.Workbench.SafeThreadAsyncCall(TestRunCompleted); - } - - /// - /// Clears the test results in the test tree view for all the - /// displayed projects. - /// - void ResetAllTestResultsInUnitTestsPad() - { - unitTestsPad.ResetTestResults(); - } - - TestProject GetTestProjectForProject(IProject project) - { - return unitTestsPad.GetTestProject(project); - } - - void UpdateTestResult(TestResult result) - { - TestProject testProject = GetTestProjectForProject(selectedTests.Project); - if (testProject != null) { - testProject.UpdateTestResult(result); - } - } - - 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; - } - } - } - } -} diff --git a/src/AddIns/Analysis/UnitTesting/Commands/IRunTestCommandContext.cs b/src/AddIns/Analysis/UnitTesting/Commands/IRunTestCommandContext.cs deleted file mode 100644 index 7621e22718..0000000000 --- a/src/AddIns/Analysis/UnitTesting/Commands/IRunTestCommandContext.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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 ICSharpCode.Core; -using ICSharpCode.SharpDevelop.Gui; -using ICSharpCode.SharpDevelop.Project; - -namespace ICSharpCode.UnitTesting -{ - public interface IRunTestCommandContext - { - IRegisteredTestFrameworks RegisteredTestFrameworks { get; } - IUnitTestTaskService TaskService { get; } - IUnitTestWorkbench Workbench { get; } - IBuildProjectFactory BuildProjectFactory { get; } - IBuildOptions BuildOptions { get; } - MessageViewCategory UnitTestCategory { get; } - IUnitTestsPad OpenUnitTestsPad { get; } - IMessageService MessageService { get; } - IUnitTestSaveAllFilesCommand SaveAllFilesCommand { get; } - IStatusBarService StatusBarService { get; } - Solution OpenSolution { get; } - } -} diff --git a/src/AddIns/Analysis/UnitTesting/Commands/PadCommands.cs b/src/AddIns/Analysis/UnitTesting/Commands/PadCommands.cs new file mode 100644 index 0000000000..6ccce2bb70 --- /dev/null +++ b/src/AddIns/Analysis/UnitTesting/Commands/PadCommands.cs @@ -0,0 +1,56 @@ +// 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.Linq; +using ICSharpCode.Core; +using ICSharpCode.SharpDevelop.Project; +using ICSharpCode.TreeView; + +namespace ICSharpCode.UnitTesting +{ + public class AddNUnitReferenceCommand : AbstractMenuCommand + { + public void Run(IProject project) + { + if (project != null) { + ReferenceProjectItem nunitRef = new ReferenceProjectItem(project, "nunit.framework"); + ProjectService.AddProjectItem(project, nunitRef); + project.Save(); + } + } + + public override void Run() + { + Run(ProjectService.CurrentProject); + } + } + + public class GotoDefinitionCommand : AbstractMenuCommand + { + public override void Run() + { + ITestTreeView treeView = Owner as ITestTreeView; + if (treeView != null) { + ITest test = treeView.SelectedTests.FirstOrDefault(); + if (test != null && test.SupportsGoToDefinition) + test.GoToDefinition(); + } + } + } + + public class CollapseAllTestsCommand : AbstractMenuCommand + { + public override void Run() + { + if (!(this.Owner is SharpTreeView)) + return; + + var treeView = (SharpTreeView)this.Owner; + if (treeView.Root != null) { + foreach (var n in treeView.Root.Descendants()) + n.IsExpanded = false; + } + } + } +} diff --git a/src/AddIns/Analysis/UnitTesting/Commands/RunAllTestsInPadCommand.cs b/src/AddIns/Analysis/UnitTesting/Commands/RunAllTestsInPadCommand.cs deleted file mode 100644 index 8f57ff0a83..0000000000 --- a/src/AddIns/Analysis/UnitTesting/Commands/RunAllTestsInPadCommand.cs +++ /dev/null @@ -1,26 +0,0 @@ -// 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; - -namespace ICSharpCode.UnitTesting -{ - public class RunAllTestsInPadCommand : RunTestInPadCommand - { - public RunAllTestsInPadCommand() - { - } - - public RunAllTestsInPadCommand(IRunTestCommandContext context) - : base(context) - { - } - - public override void Run() - { - // To make sure all tests are run we set the Owner to null. - Owner = null; - base.Run(); - } - } -} diff --git a/src/AddIns/Analysis/UnitTesting/Commands/RunProjectTestsInPadCommand.cs b/src/AddIns/Analysis/UnitTesting/Commands/RunProjectTestsInPadCommand.cs deleted file mode 100644 index ad01f23285..0000000000 --- a/src/AddIns/Analysis/UnitTesting/Commands/RunProjectTestsInPadCommand.cs +++ /dev/null @@ -1,42 +0,0 @@ -// 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 ICSharpCode.SharpDevelop.Project; - -namespace ICSharpCode.UnitTesting -{ - public class RunProjectTestsInPadCommand : RunTestInPadCommand, ITestTreeView - { - public RunProjectTestsInPadCommand() - { - } - - public RunProjectTestsInPadCommand(IRunTestCommandContext context) - : base(context) - { - } - - public override void Run() - { - Owner = this; - base.Run(); - } - - public TestMember SelectedMember { - get { return null; } - } - - public TestClass SelectedClass { - get { return null; } - } - - public TestProject SelectedProject { - get { return TestService.Solution.GetTestProject(ProjectService.CurrentProject); } - } - - public string SelectedNamespace { - get { return null; } - } - } -} diff --git a/src/AddIns/Analysis/UnitTesting/Commands/RunTestCommandContext.cs b/src/AddIns/Analysis/UnitTesting/Commands/RunTestCommandContext.cs deleted file mode 100644 index c2d00a4fc0..0000000000 --- a/src/AddIns/Analysis/UnitTesting/Commands/RunTestCommandContext.cs +++ /dev/null @@ -1,68 +0,0 @@ -// 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 ICSharpCode.Core; -using ICSharpCode.SharpDevelop; -using ICSharpCode.SharpDevelop.Gui; -using ICSharpCode.SharpDevelop.Project; - -namespace ICSharpCode.UnitTesting -{ - public class RunTestCommandContext : IRunTestCommandContext - { - UnitTestingOptions options = UnitTestingOptions.Instance.Clone(); - IRegisteredTestFrameworks testFrameworks = TestService.RegisteredTestFrameworks; - UnitTestTaskService taskService = new UnitTestTaskService(); - UnitTestWorkbench workbench = new UnitTestWorkbench(); - UnitTestBuildProjectFactory buildProjectFactory = new UnitTestBuildProjectFactory(); - UnitTestBuildOptions buildOptions = new UnitTestBuildOptions(); - MessageViewCategory unitTestCategory = TestService.UnitTestMessageView; - UnitTestSaveAllFilesCommand saveAllFilesCommand = new UnitTestSaveAllFilesCommand(); - IStatusBarService statusBarService = SD.StatusBar; - - public IRegisteredTestFrameworks RegisteredTestFrameworks { - get { return testFrameworks; } - } - - public IUnitTestTaskService TaskService { - get { return taskService; } - } - - public IUnitTestWorkbench Workbench { - get { return workbench; } - } - - public IBuildProjectFactory BuildProjectFactory { - get { return buildProjectFactory; } - } - - public IBuildOptions BuildOptions { - get { return buildOptions; } - } - - public MessageViewCategory UnitTestCategory { - get { return unitTestCategory; } - } - - public IUnitTestsPad OpenUnitTestsPad { - get { return null; } - } - - public IMessageService MessageService { - get { return SD.MessageService; } - } - - public IUnitTestSaveAllFilesCommand SaveAllFilesCommand { - get { return saveAllFilesCommand; } - } - - public IStatusBarService StatusBarService { - get { return statusBarService; } - } - - public Solution OpenSolution { - get { return ProjectService.OpenSolution; } - } - } -} diff --git a/src/AddIns/Analysis/UnitTesting/Commands/RunTestInPadCommand.cs b/src/AddIns/Analysis/UnitTesting/Commands/RunTestInPadCommand.cs deleted file mode 100644 index 6da879c82e..0000000000 --- a/src/AddIns/Analysis/UnitTesting/Commands/RunTestInPadCommand.cs +++ /dev/null @@ -1,26 +0,0 @@ -// 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 ICSharpCode.SharpDevelop.Project; -using ICSharpCode.SharpDevelop.Util; - -namespace ICSharpCode.UnitTesting -{ - public class RunTestInPadCommand : AbstractRunTestCommand - { - public RunTestInPadCommand() - { - } - - public RunTestInPadCommand(IRunTestCommandContext context) - : base(context) - { - } - - protected override ITestRunner CreateTestRunner(IProject project) - { - return Context.RegisteredTestFrameworks.CreateTestRunner(project); - } - } -} diff --git a/src/AddIns/Analysis/UnitTesting/Commands/RunTestWithDebuggerCommand.cs b/src/AddIns/Analysis/UnitTesting/Commands/RunTestWithDebuggerCommand.cs deleted file mode 100644 index a4b0b7ca78..0000000000 --- a/src/AddIns/Analysis/UnitTesting/Commands/RunTestWithDebuggerCommand.cs +++ /dev/null @@ -1,26 +0,0 @@ -// 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.Diagnostics; -using ICSharpCode.SharpDevelop.Project; - -namespace ICSharpCode.UnitTesting -{ - public class RunTestWithDebuggerCommand : AbstractRunTestCommand - { - public RunTestWithDebuggerCommand() - { - } - - public RunTestWithDebuggerCommand(IRunTestCommandContext context) - : base(context) - { - } - - protected override ITestRunner CreateTestRunner(IProject project) - { - return Context.RegisteredTestFrameworks.CreateTestDebugger(project); - } - } -} diff --git a/src/AddIns/Analysis/UnitTesting/Commands/RunningTestsCondition.cs b/src/AddIns/Analysis/UnitTesting/Commands/RunningTestsCondition.cs index e1b2e6a00c..ac046a2a81 100644 --- a/src/AddIns/Analysis/UnitTesting/Commands/RunningTestsCondition.cs +++ b/src/AddIns/Analysis/UnitTesting/Commands/RunningTestsCondition.cs @@ -3,6 +3,7 @@ using System; using ICSharpCode.Core; +using ICSharpCode.SharpDevelop; namespace ICSharpCode.UnitTesting { @@ -13,7 +14,8 @@ namespace ICSharpCode.UnitTesting { public bool IsValid(object caller, Condition condition) { - return AbstractRunTestCommand.IsRunningTest; + ITestService testService = SD.GetRequiredService(); + return testService.IsRunningTests; } } } diff --git a/src/AddIns/Analysis/UnitTesting/Commands/TestableCondition.cs b/src/AddIns/Analysis/UnitTesting/Commands/TestableCondition.cs index af6e7d9b69..d668b550cd 100644 --- a/src/AddIns/Analysis/UnitTesting/Commands/TestableCondition.cs +++ b/src/AddIns/Analysis/UnitTesting/Commands/TestableCondition.cs @@ -2,13 +2,13 @@ // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; +using System.Collections.Generic; +using System.Linq; + using ICSharpCode.Core; using ICSharpCode.NRefactory.TypeSystem; -using ICSharpCode.NRefactory.TypeSystem.Implementation; using ICSharpCode.SharpDevelop; -using ICSharpCode.SharpDevelop.Bookmarks; using ICSharpCode.SharpDevelop.Editor.Commands; -using ICSharpCode.SharpDevelop.Project; namespace ICSharpCode.UnitTesting { @@ -17,90 +17,36 @@ namespace ICSharpCode.UnitTesting /// public class TestableCondition : IConditionEvaluator { - IRegisteredTestFrameworks testFrameworks; - - public TestableCondition(IRegisteredTestFrameworks testFrameworks) - { - this.testFrameworks = testFrameworks; - } + readonly ITestService testService; public TestableCondition() - : this(TestService.RegisteredTestFrameworks) - { - } - - public static IMember GetMember(object caller) - { - ITestTreeView testTreeView = caller as ITestTreeView; - if (testTreeView != null && testTreeView.SelectedMember != null) { - return testTreeView.SelectedMember.Resolve(testTreeView.SelectedProject); - } - IEntity entity = ResolveResultMenuCommand.GetEntity(caller); - return entity as IMember; - } - - public static ITypeDefinition GetClass(object caller) - { - ITestTreeView testTreeView = caller as ITestTreeView; - if (testTreeView != null && testTreeView.SelectedClass != null) { - return testTreeView.SelectedClass.Resolve(testTreeView.SelectedProject); - } - IEntity entity = ResolveResultMenuCommand.GetEntity(caller); - return entity as ITypeDefinition; - } - - public static IProject GetProject(object caller) { - ITestTreeView testTreeView = caller as ITestTreeView; - if (testTreeView != null) { - return testTreeView.SelectedProject != null ? testTreeView.SelectedProject.Project : null; - } - ITypeDefinition c = GetClassFromMemberOrCaller(caller); - return GetProject(c); + this.testService = SD.GetRequiredService(); } - public static ITypeDefinition GetClassFromMemberOrCaller(object caller) + public TestableCondition(ITestService testService) { - IMember m = GetMember(caller); - if (m != null) { - return m.DeclaringTypeDefinition; - } - return GetClass(caller); + this.testService = testService; } - static IProject GetProject(ITypeDefinition c) + public bool IsValid(object caller, Condition condition) { - return c != null ? c.ParentAssembly.GetProject() : null; + return GetTests(testService.OpenSolution, caller).Any(); } - /// - /// Returns the namespace selected if any. - /// - public static string GetNamespace(object caller) + public static IEnumerable GetTests(ITestSolution testSolution, object caller) { ITestTreeView testTreeView = caller as ITestTreeView; if (testTreeView != null) { - return testTreeView.SelectedNamespace; + return testTreeView.SelectedTests; } - return null; - } - - public bool IsValid(object caller, Condition condition) - { - IMember m = GetMember(caller); - if (m != null) { - return testFrameworks.IsTestMember(m); - } - ITypeDefinition c = GetClass(caller); - if (ClassHasProject(c)) { - return testFrameworks.IsTestClass(c); + if (testSolution != null) { + IEntity entity = ResolveResultMenuCommand.GetEntity(caller); + ITest test = testSolution.GetTestForEntity(entity); + if (test != null) + return new[] { test }; } - return false; - } - - static bool ClassHasProject(ITypeDefinition c) - { - return (c != null) && (c.ParentAssembly.GetProject() != null); + return Enumerable.Empty(); } } } diff --git a/src/AddIns/Analysis/UnitTesting/Commands/UnitTestCommands.cs b/src/AddIns/Analysis/UnitTesting/Commands/UnitTestCommands.cs index a830a8019c..cd666366b1 100644 --- a/src/AddIns/Analysis/UnitTesting/Commands/UnitTestCommands.cs +++ b/src/AddIns/Analysis/UnitTesting/Commands/UnitTestCommands.cs @@ -2,78 +2,64 @@ // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; +using System.Collections.Generic; +using System.Linq; + using ICSharpCode.Core; -using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.SharpDevelop; -using ICSharpCode.SharpDevelop.Gui; using ICSharpCode.SharpDevelop.Project; -using ICSharpCode.TreeView; namespace ICSharpCode.UnitTesting { - public class StopTestsCommand : AbstractMenuCommand + public class RunAllTestsInProjectCommand : AbstractMenuCommand { public override void Run() { - AbstractRunTestCommand runTestCommand = AbstractRunTestCommand.RunningTestCommand; - if (runTestCommand != null) { - runTestCommand.Stop(); + ITestService testService = SD.GetRequiredService(); + if (testService.OpenSolution != null) { + ITestProject project = testService.OpenSolution.GetTestProject(ProjectService.CurrentProject); + if (project != null) + testService.RunTestsAsync(new [] { project }, new TestExecutionOptions()).FireAndForget(); } } } - public class AddNUnitReferenceCommand : AbstractMenuCommand + public class RunAllTestsInSolutionCommand : AbstractMenuCommand { - public void Run(IProject project) + public override void Run() { - if (project != null) { - ReferenceProjectItem nunitRef = new ReferenceProjectItem(project, "nunit.framework"); - ProjectService.AddProjectItem(project, nunitRef); - project.Save(); - } + ITestService testService = SD.GetRequiredService(); + if (testService.OpenSolution != null) + testService.RunTestsAsync(new [] { testService.OpenSolution }, new TestExecutionOptions()).FireAndForget(); } - + } + + public class RunSelectedTestCommand : AbstractMenuCommand + { public override void Run() { - Run(ProjectService.CurrentProject); + ITestService testService = SD.GetRequiredService(); + IEnumerable tests = TestableCondition.GetTests(testService.OpenSolution, Owner); + testService.RunTestsAsync(tests, new TestExecutionOptions()).FireAndForget(); } } - public class GotoDefinitionCommand : AbstractMenuCommand + public class RunSelectedTestWithDebuggerCommand : AbstractMenuCommand { public override void Run() { - ITestTreeView treeView = Owner as ITestTreeView; - if (treeView != null) { - var member = treeView.SelectedMember; - var c = treeView.SelectedClass; - IEntity entity; - if (member != null) { - entity = member.Resolve(treeView.SelectedProject); - } else if (c != null) { - entity = c.Resolve(treeView.SelectedProject); - } else { - entity = null; - } - if (entity != null) { - NavigationService.NavigateTo(entity); - } - } + ITestService testService = SD.GetRequiredService(); + IEnumerable tests = TestableCondition.GetTests(testService.OpenSolution, Owner); + testService.RunTestsAsync(tests, new TestExecutionOptions { UseDebugger = true }).FireAndForget(); } } - public class CollapseAllTestsCommand : AbstractMenuCommand + public class StopTestsCommand : AbstractMenuCommand { public override void Run() { - if (!(this.Owner is SharpTreeView)) - return; - - var treeView = (SharpTreeView)this.Owner; - if (treeView.Root != null) { - foreach (var n in treeView.Root.Descendants()) - n.IsExpanded = false; - } + ITestService testService = SD.GetRequiredService(); + testService.CancelRunningTests(); } } } diff --git a/src/AddIns/Analysis/UnitTesting/Frameworks/IRegisteredTestFrameworks.cs b/src/AddIns/Analysis/UnitTesting/Frameworks/IRegisteredTestFrameworks.cs deleted file mode 100644 index b8e39e0a97..0000000000 --- a/src/AddIns/Analysis/UnitTesting/Frameworks/IRegisteredTestFrameworks.cs +++ /dev/null @@ -1,23 +0,0 @@ -// 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 ICSharpCode.NRefactory.TypeSystem; -using ICSharpCode.SharpDevelop.Project; - -namespace ICSharpCode.UnitTesting -{ - public interface IRegisteredTestFrameworks - { - ITestFramework GetTestFrameworkForProject(IProject project); - ITestRunner CreateTestRunner(IProject project); - ITestRunner CreateTestDebugger(IProject project); - - bool IsTestMember(IMember member); - bool IsTestClass(ITypeDefinition typeDefinition); - bool IsTestProject(IProject project); - - bool IsBuildNeededBeforeTestRunForProject(IProject project); - } -} diff --git a/src/AddIns/Analysis/UnitTesting/Frameworks/ITestFramework.cs b/src/AddIns/Analysis/UnitTesting/Frameworks/ITestFramework.cs index e5281ea8f5..af59cc24a9 100644 --- a/src/AddIns/Analysis/UnitTesting/Frameworks/ITestFramework.cs +++ b/src/AddIns/Analysis/UnitTesting/Frameworks/ITestFramework.cs @@ -10,15 +10,25 @@ namespace ICSharpCode.UnitTesting { public interface ITestFramework { + /// + /// Gets whether this test framework supports the specified project. + /// + bool IsTestProject(IProject project); + + /// + /// Creates a new test project based on the specified project. + /// + ITestProject CreateTestProject(ITestSolution parentSolution, IProject project); + + /* bool IsTestMember(IMember member); bool IsTestClass(ITypeDefinition testClass); - bool IsTestProject(IProject project); IEnumerable GetTestMembersFor(ITypeDefinition typeDefinition); ITestRunner CreateTestRunner(); ITestRunner CreateTestDebugger(); - bool IsBuildNeededBeforeTestRun { get; } + bool IsBuildNeededBeforeTestRun { get; }*/ } } diff --git a/src/AddIns/Analysis/UnitTesting/Frameworks/ITestFrameworkFactory.cs b/src/AddIns/Analysis/UnitTesting/Frameworks/ITestFrameworkFactory.cs deleted file mode 100644 index 8f240a664d..0000000000 --- a/src/AddIns/Analysis/UnitTesting/Frameworks/ITestFrameworkFactory.cs +++ /dev/null @@ -1,12 +0,0 @@ -// 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; - -namespace ICSharpCode.UnitTesting -{ - public interface ITestFrameworkFactory - { - ITestFramework Create(string className); - } -} diff --git a/src/AddIns/Analysis/UnitTesting/Frameworks/ITestService.cs b/src/AddIns/Analysis/UnitTesting/Frameworks/ITestService.cs new file mode 100644 index 0000000000..16ffb6cdb6 --- /dev/null +++ b/src/AddIns/Analysis/UnitTesting/Frameworks/ITestService.cs @@ -0,0 +1,51 @@ +// 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.Threading.Tasks; +using ICSharpCode.Core; +using ICSharpCode.SharpDevelop.Gui; +using ICSharpCode.SharpDevelop.Project; + +namespace ICSharpCode.UnitTesting +{ + [SDService] + public interface ITestService + { + /// + /// Gets the test framework that supports the specified project. + /// + ITestFramework GetTestFrameworkForProject(IProject project); + + MessageViewCategory UnitTestMessageView { get; } + + /// + /// Gets the current test solution. + /// This property should only be accessed by the main thread. + /// + ITestSolution OpenSolution { get; } + + /// + /// Occurs when the open test solution has changed. + /// + event EventHandler OpenSolutionChanged; + + /// + /// Builds the project (if necessary) and runs the specified tests. + /// If tests are already running, the existing run is cancelled. + /// + Task RunTestsAsync(IEnumerable selectedTests, TestExecutionOptions options); + + /// + /// Gets whether tests are currently running. + /// + bool IsRunningTests { get; } + + /// + /// Aborts the current test run. + /// This method has no effect if no tests are running. + /// + void CancelRunningTests(); + } +} diff --git a/src/AddIns/Analysis/UnitTesting/Frameworks/RegisteredTestFrameworks.cs b/src/AddIns/Analysis/UnitTesting/Frameworks/RegisteredTestFrameworks.cs deleted file mode 100644 index 55f46a2d28..0000000000 --- a/src/AddIns/Analysis/UnitTesting/Frameworks/RegisteredTestFrameworks.cs +++ /dev/null @@ -1,99 +0,0 @@ -// 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.Linq; - -using ICSharpCode.Core; -using ICSharpCode.NRefactory.TypeSystem; -using ICSharpCode.SharpDevelop; -using ICSharpCode.SharpDevelop.Project; - -namespace ICSharpCode.UnitTesting -{ - public class RegisteredTestFrameworks : IRegisteredTestFrameworks - { - IReadOnlyList testFrameworkDescriptors; - public const string AddInPath = "/SharpDevelop/UnitTesting/TestFrameworks"; - - public RegisteredTestFrameworks(IAddInTree addInTree) - { - testFrameworkDescriptors = addInTree.BuildItems(AddInPath, null); - } - - public ITestFramework GetTestFrameworkForProject(IProject project) - { - if (project != null) { - foreach (TestFrameworkDescriptor descriptor in testFrameworkDescriptors) { - if (descriptor.IsSupportedProject(project) && descriptor.TestFramework.IsTestProject(project)) { - return descriptor.TestFramework; - } - } - } - return null; - } - - public bool IsTestMember(IMember member) - { - ITestFramework testFramework = GetTestFramework(member); - if (testFramework != null) { - return testFramework.IsTestMember(member); - } - return false; - } - - ITestFramework GetTestFramework(IEntity entity) - { - if (entity != null) { - return GetTestFrameworkForProject(entity.ParentAssembly.GetProject()); - } - return null; - } - - public bool IsTestClass(ITypeDefinition c) - { - ITestFramework testFramework = GetTestFramework(c); - if (testFramework != null) { - return testFramework.IsTestClass(c); - } - return false; - } - - public bool IsTestProject(IProject project) - { - ITestFramework testFramework = GetTestFrameworkForProject(project); - if (testFramework != null) { - return testFramework.IsTestProject(project); - } - return false; - } - - public ITestRunner CreateTestRunner(IProject project) - { - ITestFramework testFramework = GetTestFrameworkForProject(project); - if (testFramework != null) { - return testFramework.CreateTestRunner(); - } - return null; - } - - public ITestRunner CreateTestDebugger(IProject project) - { - ITestFramework testFramework = GetTestFrameworkForProject(project); - if (testFramework != null) { - return testFramework.CreateTestDebugger(); - } - return null; - } - - public bool IsBuildNeededBeforeTestRunForProject(IProject project) - { - ITestFramework testFramework = GetTestFrameworkForProject(project); - if (testFramework != null) { - return testFramework.IsBuildNeededBeforeTestRun; - } - return true; - } - } -} diff --git a/src/AddIns/Analysis/UnitTesting/Frameworks/SDTestService.cs b/src/AddIns/Analysis/UnitTesting/Frameworks/SDTestService.cs new file mode 100644 index 0000000000..28577dd38c --- /dev/null +++ b/src/AddIns/Analysis/UnitTesting/Frameworks/SDTestService.cs @@ -0,0 +1,88 @@ +// 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.Threading; +using System.Threading.Tasks; +using ICSharpCode.SharpDevelop; +using ICSharpCode.SharpDevelop.Gui; +using ICSharpCode.SharpDevelop.Project; +using ICSharpCode.UnitTesting.Frameworks; + +namespace ICSharpCode.UnitTesting +{ + sealed class SDTestService : ITestService + { + #region Test Framework Management + const string AddInPath = "/SharpDevelop/UnitTesting/TestFrameworks"; + IReadOnlyList testFrameworkDescriptors = SD.AddInTree.BuildItems(AddInPath, null); + + public ITestFramework GetTestFrameworkForProject(IProject project) + { + if (project != null) { + foreach (TestFrameworkDescriptor descriptor in testFrameworkDescriptors) { + if (descriptor.IsSupportedProject(project)) { + return descriptor.TestFramework; + } + } + } + return null; + } + #endregion + + #region UnitTestMessageView + MessageViewCategory unitTestMessageView; + + public MessageViewCategory UnitTestMessageView { + get { + if (unitTestMessageView == null) { + MessageViewCategory.Create(ref unitTestMessageView, + "UnitTesting", + "${res:ICSharpCode.NUnitPad.NUnitPadContent.PadName}"); + } + return unitTestMessageView; + } + } + #endregion + + #region OpenSolution + TestSolution solution; + + public ITestSolution OpenSolution { + get { + SD.MainThread.VerifyAccess(); + if (solution == null) + solution = new TestSolution(this, SD.ResourceService); + return solution; + } + } + + public event EventHandler OpenSolutionChanged { add {} remove {} } + #endregion + + #region RunTests + CancellationTokenSource runTestsCancellationTokenSource; + + public bool IsRunningTests { + get { return runTestsCancellationTokenSource != null; } + } + + public Task RunTestsAsync(IEnumerable selectedTests, TestExecutionOptions options) + { + CancelRunningTests(); + runTestsCancellationTokenSource = new CancellationTokenSource(); + var executionManager = new TestExecutionManager(); + return executionManager.RunTestsAsync(selectedTests, options, runTestsCancellationTokenSource.Token); + } + + public void CancelRunningTests() + { + if (runTestsCancellationTokenSource != null) { + runTestsCancellationTokenSource.Cancel(); + runTestsCancellationTokenSource = null; + } + } + #endregion + } +} diff --git a/src/AddIns/Analysis/UnitTesting/Frameworks/TestExecutionManager.cs b/src/AddIns/Analysis/UnitTesting/Frameworks/TestExecutionManager.cs new file mode 100644 index 0000000000..f5c5db984c --- /dev/null +++ b/src/AddIns/Analysis/UnitTesting/Frameworks/TestExecutionManager.cs @@ -0,0 +1,149 @@ +// 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; + +namespace ICSharpCode.UnitTesting.Frameworks +{ + /// + /// Manages the execution of tests across multiple projects. + /// Takes care of building the projects (if necessary) and showing progress in the UI. + /// + public class TestExecutionManager + { + readonly IBuildProjectFactory buildProjectFactory; + readonly IUnitTestTaskService taskService; + readonly IUnitTestSaveAllFilesCommand saveAllFilesCommand; + readonly ITestService testService; + readonly IWorkbench workbench; + readonly IStatusBarService statusBarService; + readonly IBuildOptions buildOptions; + + public TestExecutionManager() + { + this.buildProjectFactory = new UnitTestBuildProjectFactory(); + this.taskService = new UnitTestTaskService(); + this.saveAllFilesCommand = new UnitTestSaveAllFilesCommand(); + this.testService = SD.GetRequiredService(); + this.workbench = SD.Workbench; + this.statusBarService = SD.StatusBar; + this.buildOptions = new UnitTestBuildOptions(); + } + + readonly MultiDictionary testsByProject = new MultiDictionary(); + CancellationToken cancellationToken; + + public async Task RunTestsAsync(IEnumerable 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.Select(p => p.GetBuildableForTesting()).Where(b => b != null).ToList(); + if (projectsToBuild.Count > 0) { + var buildCommand = buildProjectFactory.CreateBuildProjectBeforeTestRun(projectsToBuild); + var buildResults = await buildCommand.BuildAsync(cancellationToken); + if (buildResults.Result != BuildResultCode.Success) + return; + } + + cancellationToken.ThrowIfCancellationRequested(); + using (IProgressMonitor progressMonitor = statusBarService.CreateProgressMonitor(cancellationToken)) { + int projectsLeftToRun = testsByProject.Count; + foreach (IGrouping g in testsByProject) { + ITestProject project = g.Key; + progressMonitor.TaskName = GetProgressMonitorLabel(project); + progressMonitor.Progress = GetProgress(projectsLeftToRun); + using (IProgressMonitor nested = progressMonitor.CreateSubTask(1.0 / testsByProject.Count)) { + await project.RunTestsAsync(g, options, nested); + } + projectsLeftToRun--; + progressMonitor.CancellationToken.ThrowIfCancellationRequested(); + } + } + + ShowErrorList(); + } + + void GroupTestsByProject(IEnumerable 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.ClearText(); + taskService.ClearExceptCommentTasks(); + testService.UnitTestMessageView.ClearText(); + } + + void ShowUnitTestsPad() + { + workbench.GetPad(typeof(UnitTestsPad)).BringPadToFront(); + } + + void ShowOutputPad() + { + workbench.GetPad(typeof(CompilerMessageView)).BringPadToFront(); + } + + 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 ShowErrorList() + { + if (taskService.SomethingWentWrong && buildOptions.ShowErrorListAfterBuild) { + workbench.GetPad(typeof(ErrorListPad)).BringPadToFront(); + } + } + } +} diff --git a/src/AddIns/Analysis/UnitTesting/Frameworks/TestExecutionOptions.cs b/src/AddIns/Analysis/UnitTesting/Frameworks/TestExecutionOptions.cs new file mode 100644 index 0000000000..63834da005 --- /dev/null +++ b/src/AddIns/Analysis/UnitTesting/Frameworks/TestExecutionOptions.cs @@ -0,0 +1,19 @@ +// 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; + +namespace ICSharpCode.UnitTesting +{ + /// + /// Options used for test execution. + /// + public class TestExecutionOptions + { + /// + /// Gets/Sets whether to debug the test execution. + /// The default is false. + /// + public bool UseDebugger { get; set; } + } +} diff --git a/src/AddIns/Analysis/UnitTesting/Frameworks/TestFrameworkDescriptor.cs b/src/AddIns/Analysis/UnitTesting/Frameworks/TestFrameworkDescriptor.cs index 98075981a8..0f62ac2639 100644 --- a/src/AddIns/Analysis/UnitTesting/Frameworks/TestFrameworkDescriptor.cs +++ b/src/AddIns/Analysis/UnitTesting/Frameworks/TestFrameworkDescriptor.cs @@ -12,14 +12,14 @@ namespace ICSharpCode.UnitTesting public class TestFrameworkDescriptor { Properties properties; - ITestFrameworkFactory factory; + Func objectFactory; ITestFramework testFramework; List supportedProjectFileExtensions = new List(); - public TestFrameworkDescriptor(Properties properties, ITestFrameworkFactory factory) + public TestFrameworkDescriptor(Properties properties, Func objectFactory) { this.properties = properties; - this.factory = factory; + this.objectFactory = objectFactory; GetSupportedProjectFileExtensions(); } @@ -47,7 +47,7 @@ namespace ICSharpCode.UnitTesting void CreateTestFrameworkIfNotCreated() { if (testFramework == null) { - testFramework = factory.Create(ClassName); + testFramework = (ITestFramework)objectFactory(ClassName); } } diff --git a/src/AddIns/Analysis/UnitTesting/Frameworks/TestFrameworkDoozer.cs b/src/AddIns/Analysis/UnitTesting/Frameworks/TestFrameworkDoozer.cs index e613f301ba..7da0a79964 100644 --- a/src/AddIns/Analysis/UnitTesting/Frameworks/TestFrameworkDoozer.cs +++ b/src/AddIns/Analysis/UnitTesting/Frameworks/TestFrameworkDoozer.cs @@ -19,12 +19,7 @@ namespace ICSharpCode.UnitTesting public object BuildItem(BuildItemArgs args) { - return BuildItem(args.Codon, new TestFrameworkFactory(args.AddIn)); - } - - public TestFrameworkDescriptor BuildItem(Codon codon, ITestFrameworkFactory factory) - { - return new TestFrameworkDescriptor(codon.Properties, factory); + return new TestFrameworkDescriptor(args.Codon.Properties, args.AddIn.CreateObject); } } } diff --git a/src/AddIns/Analysis/UnitTesting/Frameworks/TestFrameworkFactory.cs b/src/AddIns/Analysis/UnitTesting/Frameworks/TestFrameworkFactory.cs deleted file mode 100644 index a23e8ea7eb..0000000000 --- a/src/AddIns/Analysis/UnitTesting/Frameworks/TestFrameworkFactory.cs +++ /dev/null @@ -1,23 +0,0 @@ -// 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 ICSharpCode.Core; - -namespace ICSharpCode.UnitTesting -{ - public class TestFrameworkFactory : ITestFrameworkFactory - { - AddIn addin; - - public TestFrameworkFactory(AddIn addin) - { - this.addin = addin; - } - - public ITestFramework Create(string className) - { - return addin.CreateObject(className) as ITestFramework; - } - } -} diff --git a/src/AddIns/Analysis/UnitTesting/Frameworks/TestService.cs b/src/AddIns/Analysis/UnitTesting/Frameworks/TestService.cs deleted file mode 100644 index 138da43589..0000000000 --- a/src/AddIns/Analysis/UnitTesting/Frameworks/TestService.cs +++ /dev/null @@ -1,57 +0,0 @@ -// 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 ICSharpCode.SharpDevelop; -using ICSharpCode.SharpDevelop.Gui; - -namespace ICSharpCode.UnitTesting -{ - public static class TestService - { - static IRegisteredTestFrameworks testFrameworks; - static MessageViewCategory unitTestMessageView; - - public static IRegisteredTestFrameworks RegisteredTestFrameworks { - get { - CreateRegisteredTestFrameworks(); - return testFrameworks; - } - set { testFrameworks = value; } - } - - static void CreateRegisteredTestFrameworks() - { - if (testFrameworks == null) { - testFrameworks = new RegisteredTestFrameworks(SD.AddInTree); - } - } - - public static MessageViewCategory UnitTestMessageView { - get { - if (unitTestMessageView == null) { - CreateUnitTestCategory(); - } - return unitTestMessageView; - } - } - - static void CreateUnitTestCategory() - { - MessageViewCategory.Create(ref unitTestMessageView, - "UnitTesting", - "${res:ICSharpCode.NUnitPad.NUnitPadContent.PadName}"); - } - - static TestSolution solution; - - public static TestSolution Solution { - get { - SD.MainThread.VerifyAccess(); - if (solution == null) - solution = new TestSolution(RegisteredTestFrameworks); - return solution; - } - } - } -} diff --git a/src/AddIns/Analysis/UnitTesting/Interfaces/IBuildProjectFactory.cs b/src/AddIns/Analysis/UnitTesting/Interfaces/IBuildProjectFactory.cs index 6d3f18487e..dd76aa6930 100644 --- a/src/AddIns/Analysis/UnitTesting/Interfaces/IBuildProjectFactory.cs +++ b/src/AddIns/Analysis/UnitTesting/Interfaces/IBuildProjectFactory.cs @@ -10,6 +10,6 @@ namespace ICSharpCode.UnitTesting { public interface IBuildProjectFactory { - BuildProject CreateBuildProjectBeforeTestRun(IEnumerable projects); + BuildProject CreateBuildProjectBeforeTestRun(IEnumerable projects); } } diff --git a/src/AddIns/Analysis/UnitTesting/Interfaces/IUnitTestTaskService.cs b/src/AddIns/Analysis/UnitTesting/Interfaces/IUnitTestTaskService.cs index eed3c79814..0cc610d47e 100644 --- a/src/AddIns/Analysis/UnitTesting/Interfaces/IUnitTestTaskService.cs +++ b/src/AddIns/Analysis/UnitTesting/Interfaces/IUnitTestTaskService.cs @@ -11,7 +11,6 @@ namespace ICSharpCode.UnitTesting public interface IUnitTestTaskService { MessageViewCategory BuildMessageViewCategory { get; } - bool InUpdate { get; set; } void ClearExceptCommentTasks(); void Add(SDTask task); bool SomethingWentWrong { get; } diff --git a/src/AddIns/Analysis/UnitTesting/Interfaces/IUnitTestWorkbench.cs b/src/AddIns/Analysis/UnitTesting/Interfaces/IUnitTestWorkbench.cs deleted file mode 100644 index eb3541512e..0000000000 --- a/src/AddIns/Analysis/UnitTesting/Interfaces/IUnitTestWorkbench.cs +++ /dev/null @@ -1,15 +0,0 @@ -// 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 ICSharpCode.SharpDevelop; - -namespace ICSharpCode.UnitTesting -{ - public interface IUnitTestWorkbench - { - PadDescriptor GetPad(Type type); - void SafeThreadAsyncCall(Action method); - void SafeThreadAsyncCall(Action method, A arg1); - } -} diff --git a/src/AddIns/Analysis/UnitTesting/Interfaces/UnitTestBuildProjectFactory.cs b/src/AddIns/Analysis/UnitTesting/Interfaces/UnitTestBuildProjectFactory.cs index 9f78ae9118..c3cd89c71a 100644 --- a/src/AddIns/Analysis/UnitTesting/Interfaces/UnitTestBuildProjectFactory.cs +++ b/src/AddIns/Analysis/UnitTesting/Interfaces/UnitTestBuildProjectFactory.cs @@ -10,7 +10,7 @@ namespace ICSharpCode.UnitTesting { public class UnitTestBuildProjectFactory : IBuildProjectFactory { - public BuildProject CreateBuildProjectBeforeTestRun(IEnumerable projects) + public BuildProject CreateBuildProjectBeforeTestRun(IEnumerable projects) { return new BuildProjectBeforeExecute(new MultipleProjectBuildable(projects)); } diff --git a/src/AddIns/Analysis/UnitTesting/Interfaces/UnitTestTaskService.cs b/src/AddIns/Analysis/UnitTesting/Interfaces/UnitTestTaskService.cs index de2078a6a4..1ba24447ad 100644 --- a/src/AddIns/Analysis/UnitTesting/Interfaces/UnitTestTaskService.cs +++ b/src/AddIns/Analysis/UnitTesting/Interfaces/UnitTestTaskService.cs @@ -14,11 +14,6 @@ namespace ICSharpCode.UnitTesting TaskService.ClearExceptCommentTasks(); } - public bool InUpdate { - get { return TaskService.InUpdate; } - set { TaskService.InUpdate = value; } - } - public MessageViewCategory BuildMessageViewCategory { get { return TaskService.BuildMessageViewCategory; } } diff --git a/src/AddIns/Analysis/UnitTesting/Interfaces/UnitTestWorkbench.cs b/src/AddIns/Analysis/UnitTesting/Interfaces/UnitTestWorkbench.cs deleted file mode 100644 index 6e2182c6ab..0000000000 --- a/src/AddIns/Analysis/UnitTesting/Interfaces/UnitTestWorkbench.cs +++ /dev/null @@ -1,27 +0,0 @@ -// 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 ICSharpCode.SharpDevelop; -using ICSharpCode.SharpDevelop.Gui; - -namespace ICSharpCode.UnitTesting -{ - public class UnitTestWorkbench : IUnitTestWorkbench - { - public PadDescriptor GetPad(Type type) - { - return WorkbenchSingleton.Workbench.GetPad(type); - } - - public void SafeThreadAsyncCall(Action method) - { - WorkbenchSingleton.SafeThreadAsyncCall(method); - } - - public void SafeThreadAsyncCall(Action method, A arg1) - { - WorkbenchSingleton.SafeThreadAsyncCall(method, arg1); - } - } -} diff --git a/src/AddIns/Analysis/UnitTesting/Model/ITest.cs b/src/AddIns/Analysis/UnitTesting/Model/ITest.cs new file mode 100644 index 0000000000..a86d28494c --- /dev/null +++ b/src/AddIns/Analysis/UnitTesting/Model/ITest.cs @@ -0,0 +1,55 @@ +// 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.ObjectModel; + +namespace ICSharpCode.UnitTesting +{ + /// + /// Represents a unit test or a group of unit tests. + /// + public interface ITest + { + /// + /// Gets the collection of nested tests. + /// + TestCollection NestedTests { get; } + + /// + /// Gets the parent project that owns this test. + /// + ITestProject ParentProject { get; } + + /// + /// Name to be displayed in the tests tree view. + /// + string DisplayName { get; } + + /// + /// Raised when the property changes. + /// + event EventHandler DisplayNameChanged; + + /// + /// Gets the result of the previous run of this test. + /// + TestResultType Result { get; } + + /// + /// Raised when the property changes. + /// + event EventHandler ResultChanged; + + /// + /// Resets the test results for this test and all nested tests. + /// + void ResetTestResults(); + + bool SupportsGoToDefinition { get; } + + void GoToDefinition(); + + UnitTestNode CreateTreeNode(); + } +} diff --git a/src/AddIns/Analysis/UnitTesting/Model/ITestProject.cs b/src/AddIns/Analysis/UnitTesting/Model/ITestProject.cs new file mode 100644 index 0000000000..d297511f89 --- /dev/null +++ b/src/AddIns/Analysis/UnitTesting/Model/ITestProject.cs @@ -0,0 +1,47 @@ +// 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.Threading; +using System.Threading.Tasks; +using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.SharpDevelop.Gui; +using ICSharpCode.SharpDevelop.Project; + +namespace ICSharpCode.UnitTesting +{ + /// + /// Represents the root node of a test project. + /// + public interface ITestProject : ITest + { + /// + /// Gets the SharpDevelop project on which this test project is based. + /// + IProject Project { get; } + + /// + /// Gets the test for the specified entity. + /// Returns null if the entity is not a unit test. + /// + ITest GetTestForEntity(IEntity entity); + + /// + /// Returns a SharpDevelop that builds the project + /// for test execution. + /// May return null if the project does not require compilation. + /// + IBuildable GetBuildableForTesting(); + + /// + /// Notifies the project that the parse information was changed. + /// + void NotifyParseInformationChanged(IUnresolvedFile oldUnresolvedFile, IUnresolvedFile newUnresolvedFile); + + /// + /// Runs the specified tests. The specified tests must belong to this project. + /// + Task RunTestsAsync(IEnumerable tests, TestExecutionOptions options, IProgressMonitor progressMonitor); + } +} diff --git a/src/AddIns/Analysis/UnitTesting/Model/ITestSolution.cs b/src/AddIns/Analysis/UnitTesting/Model/ITestSolution.cs new file mode 100644 index 0000000000..e5d867cee0 --- /dev/null +++ b/src/AddIns/Analysis/UnitTesting/Model/ITestSolution.cs @@ -0,0 +1,33 @@ +// 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.ObjectModel; +using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.SharpDevelop.Project; + +namespace ICSharpCode.UnitTesting +{ + /// + /// A test that represents the whole solution. + /// + public interface ITestSolution : ITest + { +// /// +// /// Gets the list of all test projects. +// /// +// ObservableCollection TestableProjects { get; } + + /// + /// Gets the test project for the specified project. + /// Returns null if the project is not a test project. + /// + ITestProject GetTestProject(IProject project); + + /// + /// Gets the test for the specified entity. + /// Returns null if the entity is not a unit test. + /// + ITest GetTestForEntity(IEntity entity); + } +} diff --git a/src/AddIns/Analysis/UnitTesting/Model/TestBase.cs b/src/AddIns/Analysis/UnitTesting/Model/TestBase.cs new file mode 100644 index 0000000000..7a2df43104 --- /dev/null +++ b/src/AddIns/Analysis/UnitTesting/Model/TestBase.cs @@ -0,0 +1,143 @@ +// 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.ObjectModel; +using System.ComponentModel; + +namespace ICSharpCode.UnitTesting +{ + /// + /// Base class for implementations. + /// + public abstract class TestBase : ITest + { + public abstract string DisplayName { get; } + public virtual event EventHandler DisplayNameChanged { add {} remove {} } + + public abstract ITestProject ParentProject { get; } + + #region Result + TestResultType result; + bool useCompositeResultsOfNestedTests; + + public event EventHandler ResultChanged; + + /// + /// Gets/Sets the test result. + /// + /// + /// If the result is bound to the composite of the nested tests, setting this + /// property will remove the binding. + /// + public TestResultType Result { + get { return result; } + protected set { + if (useCompositeResultsOfNestedTests) { + nestedTests.PropertyChanged -= nestedTestsCollection_PropertyChanged; + useCompositeResultsOfNestedTests = false; + } + ChangeResult(value); + } + } + + void ChangeResult(TestResultType newResult) + { + TestResultType oldResult = result; + if (oldResult != newResult) { + result = newResult; + OnResultChanged(new TestResultTypeChangedEventArgs(oldResult, newResult)); + } + } + + /// + /// Binds the result to the composite result of the nested tests. + /// + protected void BindResultToCompositeResultOfNestedTests() + { + if (!useCompositeResultsOfNestedTests) { + useCompositeResultsOfNestedTests = true; + if (nestedTests != null) { + nestedTests.PropertyChanged += nestedTestsCollection_PropertyChanged; + ChangeResult(nestedTests.CompositeResult); + } else { + ChangeResult(TestResultType.None); + } + } + } + + void nestedTestsCollection_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == null || e.PropertyName == "CompositeResult") { + ChangeResult(nestedTests.CompositeResult); + } + } + + protected virtual void OnResultChanged(TestResultTypeChangedEventArgs e) + { + if (ResultChanged != null) { + ResultChanged(this, e); + } + } + #endregion + + #region NestedTests + TestCollection nestedTests; + + public TestCollection NestedTests { + get { + if (nestedTests == null) { + nestedTests = InitializeNestedTests(); + OnNestedTestsInitialized(); + } + return nestedTests; + } + } + + protected bool NestedTestsInitialized { + get { return nestedTests != null; } + } + + protected virtual TestCollection InitializeNestedTests() + { + return new TestCollection(); + } + + protected virtual void OnNestedTestsInitialized() + { + // If we're supposed to be bound to the composite result of the nested tests, + // but the nested test collection wasn't initialized yet, + // we will recreate the binding. + if (useCompositeResultsOfNestedTests) { + useCompositeResultsOfNestedTests = false; + BindResultToCompositeResultOfNestedTests(); + } + } + #endregion + + public virtual void ResetTestResults() + { + if (nestedTests != null) { + foreach (ITest test in nestedTests) { + test.ResetTestResults(); + } + } + if (!useCompositeResultsOfNestedTests) + this.Result = TestResultType.None; + } + + public virtual bool SupportsGoToDefinition { + get { return false; } + } + + public void GoToDefinition() + { + throw new NotSupportedException(); + } + + public virtual UnitTestNode CreateTreeNode() + { + return new UnitTestNode(this); + } + } +} diff --git a/src/AddIns/Analysis/UnitTesting/Model/TestCollection.cs b/src/AddIns/Analysis/UnitTesting/Model/TestCollection.cs new file mode 100644 index 0000000000..adbed25332 --- /dev/null +++ b/src/AddIns/Analysis/UnitTesting/Model/TestCollection.cs @@ -0,0 +1,199 @@ +// 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.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; + +namespace ICSharpCode.UnitTesting +{ + /// + /// Collection of tests that monitors the amount of tests for each result type, + /// allowing efficient updates of the overall result. + /// + public class TestCollection : ObservableCollection + { + #region Struct TestCounts + struct TestCounts + { + internal int indeterminate; + internal int successful; + internal int failed; + internal int ignored; + + public TestResultType CompositeResult { + get { + if (failed > 0) + return TestResultType.Failure; + if (indeterminate > 0) + return TestResultType.None; + if (ignored > 0) + return TestResultType.Ignored; + if (successful > 0) + return TestResultType.Success; + return TestResultType.None; + } + } + + public void Add(TestResultType result) + { + switch (result) { + case TestResultType.None: + indeterminate++; + break; + case TestResultType.Success: + successful++; + break; + case TestResultType.Failure: + failed++; + break; + case TestResultType.Ignored: + ignored++; + break; + default: + throw new NotSupportedException("Invalid value for TestResultType"); + } + } + + public void Remove(TestResultType result) + { + switch (result) { + case TestResultType.None: + indeterminate--; + break; + case TestResultType.Success: + successful--; + break; + case TestResultType.Failure: + failed--; + break; + case TestResultType.Ignored: + ignored--; + break; + default: + throw new NotSupportedException("Invalid value for TestResultType"); + } + } + } + #endregion + + public static TestResultType GetCompositeResult(IEnumerable tests) + { + TestCounts c = new TestCounts(); + foreach (ITest test in tests) { + c.Add(test.Result); + } + return c.CompositeResult; + } + + #region Properties + TestCounts counts; + + public int IndeterminateNestedTestCount { + get { return counts.indeterminate; } + } + + public int SuccessfulNestedTestCount { + get { return counts.successful; } + } + + public int FailedNestedTestCount { + get { return counts.failed; } + } + + public int IgnoredNestedTestCount { + get { return counts.ignored; } + } + + public TestResultType CompositeResult { + get { return counts.CompositeResult; } + } + + static readonly PropertyChangedEventArgs indeterminateArgs = new PropertyChangedEventArgs("IndeterminateNestedTestCount"); + static readonly PropertyChangedEventArgs successfulArgs = new PropertyChangedEventArgs("SuccessfulNestedTestCount"); + static readonly PropertyChangedEventArgs failedArgs = new PropertyChangedEventArgs("FailedNestedTestCount"); + static readonly PropertyChangedEventArgs ignoredArgs = new PropertyChangedEventArgs("IgnoredNestedTestCount"); + static readonly PropertyChangedEventArgs compositeResultArgs = new PropertyChangedEventArgs("CompositeResult"); + + void RaiseCountChangeEvents(TestCounts old) + { + if (old.indeterminate != counts.indeterminate) + OnPropertyChanged(indeterminateArgs); + if (old.successful != counts.successful) + OnPropertyChanged(successfulArgs); + if (old.failed != counts.failed) + OnPropertyChanged(failedArgs); + if (old.ignored != counts.ignored) + OnPropertyChanged(ignoredArgs); + if (old.CompositeResult != counts.CompositeResult) + OnPropertyChanged(compositeResultArgs); + } + + // change visibility of PropertyChanged event to public + public new event PropertyChangedEventHandler PropertyChanged { + add { base.PropertyChanged += value; } + remove { base.PropertyChanged -= value; } + } + #endregion + + void item_ResultChanged(object sender, TestResultTypeChangedEventArgs e) + { + TestCounts old = counts; + counts.Remove(e.OldResult); + counts.Add(e.NewResult); + RaiseCountChangeEvents(old); + } + + protected override void ClearItems() + { + TestCounts old = counts; + counts = default(TestCounts); + foreach (ITest test in Items) { + counts.Remove(test.Result); + test.ResultChanged -= item_ResultChanged; + } + base.ClearItems(); + RaiseCountChangeEvents(old); + } + + protected override void InsertItem(int index, ITest item) + { + if (item == null) + throw new ArgumentNullException("item"); + TestCounts old = counts; + counts.Add(item.Result); + base.InsertItem(index, item); + item.ResultChanged += item_ResultChanged; + RaiseCountChangeEvents(old); + } + + protected override void RemoveItem(int index) + { + TestCounts old = counts; + ITest oldItem = Items[index]; + counts.Remove(oldItem.Result); + oldItem.ResultChanged -= item_ResultChanged; + base.RemoveItem(index); + RaiseCountChangeEvents(old); + } + + protected override void SetItem(int index, ITest item) + { + if (item == null) + throw new ArgumentNullException("item"); + TestCounts old = counts; + + ITest oldItem = Items[index]; + counts.Remove(oldItem.Result); + oldItem.ResultChanged -= item_ResultChanged; + + counts.Add(item.Result); + item.ResultChanged += item_ResultChanged; + + base.SetItem(index, item); + RaiseCountChangeEvents(old); + } + } +} diff --git a/src/AddIns/Analysis/UnitTesting/Model/TestNamespace.cs b/src/AddIns/Analysis/UnitTesting/Model/TestNamespace.cs new file mode 100644 index 0000000000..eb8a4cd4f7 --- /dev/null +++ b/src/AddIns/Analysis/UnitTesting/Model/TestNamespace.cs @@ -0,0 +1,46 @@ +// 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.ObjectModel; + +namespace ICSharpCode.UnitTesting +{ + /// + /// Represents a namespace of unit tests. + /// + public class TestNamespace : TestBase + { + readonly ITestProject project; + readonly string namespaceName; + readonly string displayName; + + public TestNamespace(ITestProject project, string namespaceName, string displayName = null) + { + if (project == null) + throw new ArgumentNullException("project"); + if (namespaceName == null) + throw new ArgumentNullException("namespaceName"); + this.project = project; + this.namespaceName = namespaceName; + if (displayName != null) { + this.displayName = displayName; + } else { + this.displayName = namespaceName.Substring(namespaceName.IndexOf('.') + 1); + } + BindResultToCompositeResultOfNestedTests(); + } + + public override ITestProject ParentProject { + get { return project; } + } + + public string NamespaceName { + get { return namespaceName; } + } + + public override string DisplayName { + get { return displayName; } + } + } +} diff --git a/src/AddIns/Analysis/UnitTesting/Model/TestProjectBase.cs b/src/AddIns/Analysis/UnitTesting/Model/TestProjectBase.cs new file mode 100644 index 0000000000..e86404b281 --- /dev/null +++ b/src/AddIns/Analysis/UnitTesting/Model/TestProjectBase.cs @@ -0,0 +1,58 @@ +// 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.Threading.Tasks; +using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.SharpDevelop.Gui; +using ICSharpCode.SharpDevelop.Project; + +namespace ICSharpCode.UnitTesting +{ + /// + /// Base class for implementations. + /// + public abstract class TestProjectBase : TestBase, ITestProject + { + IProject project; + + public TestProjectBase(IProject project) + { + if (project == null) + throw new ArgumentNullException("project"); + this.project = project; + BindResultToCompositeResultOfNestedTests(); + } + + public IProject Project { + get { return project; } + } + + public override ITestProject ParentProject { + get { return this; } + } + + public override string DisplayName { + get { return project.Name; } + } + + public virtual ITest GetTestForEntity(IEntity entity) + { + return null; + } + + public virtual IBuildable GetBuildableForTesting() + { + return project; + } + + public void NotifyParseInformationChanged(IUnresolvedFile oldUnresolvedFile, IUnresolvedFile newUnresolvedFile) + { + if (!NestedTestsInitialized) + return; + } + + public abstract Task RunTestsAsync(IEnumerable tests, TestExecutionOptions options, IProgressMonitor progressMonitor); + } +} diff --git a/src/AddIns/Analysis/UnitTesting/Model/TestResultTypeChangedEventArgs.cs b/src/AddIns/Analysis/UnitTesting/Model/TestResultTypeChangedEventArgs.cs new file mode 100644 index 0000000000..c7807587ac --- /dev/null +++ b/src/AddIns/Analysis/UnitTesting/Model/TestResultTypeChangedEventArgs.cs @@ -0,0 +1,30 @@ +// 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; + +namespace ICSharpCode.UnitTesting +{ + /// + /// EventArgs for ITest.ResultChanged. + /// + public class TestResultTypeChangedEventArgs : EventArgs + { + readonly TestResultType oldResult; + readonly TestResultType newResult; + + public TestResultTypeChangedEventArgs(TestResultType oldResult, TestResultType newResult) + { + this.oldResult = oldResult; + this.newResult = newResult; + } + + public TestResultType NewResult { + get { return newResult; } + } + + public TestResultType OldResult { + get { return oldResult; } + } + } +} diff --git a/src/AddIns/Analysis/UnitTesting/Model/TestSolution.cs b/src/AddIns/Analysis/UnitTesting/Model/TestSolution.cs index 0b266ac1fe..26ee0d7f23 100644 --- a/src/AddIns/Analysis/UnitTesting/Model/TestSolution.cs +++ b/src/AddIns/Analysis/UnitTesting/Model/TestSolution.cs @@ -5,7 +5,8 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; - +using ICSharpCode.Core; +using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop.Parser; using ICSharpCode.SharpDevelop.Project; @@ -15,21 +16,20 @@ namespace ICSharpCode.UnitTesting /// /// Manages the collection of TestProjects. /// - public class TestSolution + sealed class TestSolution : TestBase, ITestSolution { - readonly IRegisteredTestFrameworks registeredTestFrameworks; + readonly IResourceService resourceService; + readonly ITestService testService; readonly List changeListeners = new List(); - readonly ObservableCollection testableProjects = new ObservableCollection(); - - public ObservableCollection TestableProjects { - get { return testableProjects; } - } - public TestSolution(IRegisteredTestFrameworks registeredTestFrameworks) + public TestSolution(ITestService testService, IResourceService resourceService) { - if (registeredTestFrameworks == null) - throw new ArgumentNullException("registeredTestFrameworks"); - this.registeredTestFrameworks = registeredTestFrameworks; + if (testService == null) + throw new ArgumentNullException("testService"); + if (resourceService == null) + throw new ArgumentNullException("resourceService"); + this.testService = testService; + this.resourceService = resourceService; ProjectService.SolutionLoaded += ProjectService_SolutionLoaded; ProjectService.SolutionClosed += ProjectService_SolutionClosed; ProjectService.ProjectAdded += ProjectService_ProjectAdded; @@ -40,13 +40,39 @@ namespace ICSharpCode.UnitTesting SD_ParserService_LoadSolutionProjectsThread_Finished(null, null); } } - - /// - /// Retrieves the TestProject for the specified project. - /// - public TestProject GetTestProject(IProject currentProject) + + public override string DisplayName { + get { return resourceService.GetString("ICSharpCode.UnitTesting.AllTestsTreeNode.Text"); } + } + + public override event EventHandler DisplayNameChanged { + add { resourceService.LanguageChanged += value; } + remove { resourceService.LanguageChanged -= value; } + } + + public override ITestProject ParentProject { + get { return null; } + } + + public ITestProject GetTestProject(IProject project) + { + for (int i = 0; i < changeListeners.Count; i++) { + if (changeListeners[i].project == project) { + return changeListeners[i].testProject; + } + } + return null; + } + + public ITest GetTestForEntity(IEntity entity) { - return testableProjects.FirstOrDefault(p => p.Project == currentProject); + if (entity == null) + return null; + ITestProject testProject = GetTestProject(entity.ParentAssembly.GetProject()); + if (testProject != null) + return testProject.GetTestForEntity(entity); + else + return null; } /// @@ -57,8 +83,9 @@ namespace ICSharpCode.UnitTesting class ProjectChangeListener { readonly TestSolution testSolution; + ITestFramework oldTestFramework; internal readonly IProject project; - TestProject testProject; + internal ITestProject testProject; public ProjectChangeListener(TestSolution testSolution, IProject project) { @@ -77,7 +104,7 @@ namespace ICSharpCode.UnitTesting project.ParseInformationUpdated -= project_ParseInformationUpdated; // Remove old testProject if (testProject != null) { - testSolution.testableProjects.Remove(testProject); + testSolution.NestedTests.Remove(testProject); testProject = null; } } @@ -91,20 +118,23 @@ namespace ICSharpCode.UnitTesting internal void CheckTestFramework() { - ITestFramework newTestFramework = testSolution.registeredTestFrameworks.GetTestFrameworkForProject(project); - if (newTestFramework != null && testProject != null && testProject.TestFramework == newTestFramework) + ITestFramework newTestFramework = testSolution.testService.GetTestFrameworkForProject(project); + if (newTestFramework == oldTestFramework) return; // test framework is unchanged // Remove old testProject if (testProject != null) { - testSolution.testableProjects.Remove(testProject); + testSolution.NestedTests.Remove(testProject); testProject = null; } // Create new testProject if (newTestFramework != null) { - testProject = new TestProject(project, newTestFramework); - testSolution.testableProjects.Add(testProject); + testProject = newTestFramework.CreateTestProject(testSolution, project); + if (testProject == null) + throw new InvalidOperationException("CreateTestProject() returned null"); + testSolution.NestedTests.Add(testProject); } + oldTestFramework = newTestFramework; } } diff --git a/src/AddIns/Analysis/UnitTesting/NUnit/NUnitConsoleApplication.cs b/src/AddIns/Analysis/UnitTesting/NUnit/NUnitConsoleApplication.cs index a8b1207b43..7e875f5528 100644 --- a/src/AddIns/Analysis/UnitTesting/NUnit/NUnitConsoleApplication.cs +++ b/src/AddIns/Analysis/UnitTesting/NUnit/NUnitConsoleApplication.cs @@ -16,18 +16,19 @@ namespace ICSharpCode.UnitTesting { public class NUnitConsoleApplication { - public NUnitConsoleApplication(SelectedTests selectedTests, UnitTestingOptions options) + /* + public NUnitConsoleApplication(ITestProject project, IEnumerable selectedTests, UnitTestingOptions options) { - Initialize(selectedTests); + Initialize(project, selectedTests); InitializeOptions(options); } - public NUnitConsoleApplication(SelectedTests selectedTests) + public NUnitConsoleApplication(ITestProject project, IEnumerable selectedTests) { - Initialize(selectedTests); + Initialize(project, selectedTests); } - void Initialize(SelectedTests selectedTests) + void Initialize(ITestProject project, IEnumerable selectedTests) { this.selectedTests = selectedTests; this.project = selectedTests.Project; @@ -42,6 +43,7 @@ namespace ICSharpCode.UnitTesting } } } + */ void InitializeOptions(UnitTestingOptions options) { @@ -150,9 +152,9 @@ namespace ICSharpCode.UnitTesting public string NamespaceFilter; IProject project; - SelectedTests selectedTests; + IEnumerable selectedTests; - public SelectedTests SelectedTests { + public IEnumerable SelectedTests { get { return selectedTests; } } diff --git a/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestDebugger.cs b/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestDebugger.cs index d964f28ab6..d0886ce178 100644 --- a/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestDebugger.cs +++ b/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestDebugger.cs @@ -2,7 +2,9 @@ // 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 ICSharpCode.Core; using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop.Debugging; @@ -30,7 +32,7 @@ namespace ICSharpCode.UnitTesting this.options = options; } - protected override ProcessStartInfo GetProcessStartInfo(SelectedTests selectedTests) + protected override ProcessStartInfo GetProcessStartInfo(IEnumerable selectedTests) { NUnitConsoleApplication app = new NUnitConsoleApplication(selectedTests, options); app.Results = base.TestResultsMonitor.FileName; diff --git a/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestFramework.cs b/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestFramework.cs index 7ba452d939..a556cbc641 100644 --- a/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestFramework.cs +++ b/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestFramework.cs @@ -14,23 +14,9 @@ namespace ICSharpCode.UnitTesting { public class NUnitTestFramework : ITestFramework { - public bool IsBuildNeededBeforeTestRun { - get { return true; } - } - - public ITestRunner CreateTestRunner() - { - return new NUnitTestRunner(); - } - - public ITestRunner CreateTestDebugger() - { - return new NUnitTestDebugger(); - } - - static readonly ITypeReference testAttributeRef = new GetClassTypeReference("NUnit.Framework", "TestAttribute", 0); - static readonly ITypeReference testCaseAttributeRef = new GetClassTypeReference("NUnit.Framework", "TestCaseAttribute", 0); - static readonly ITypeReference testFixtureAttributeRef = new GetClassTypeReference("NUnit.Framework", "TestFixtureAttribute", 0); + readonly ITypeReference testAttributeRef = new GetClassTypeReference("NUnit.Framework", "TestAttribute", 0); + readonly ITypeReference testCaseAttributeRef = new GetClassTypeReference("NUnit.Framework", "TestCaseAttribute", 0); + readonly ITypeReference testFixtureAttributeRef = new GetClassTypeReference("NUnit.Framework", "TestFixtureAttribute", 0); /// /// Determines whether the project is a test project. A project @@ -44,6 +30,23 @@ namespace ICSharpCode.UnitTesting return testAttributeRef.Resolve(SD.ParserService.GetCompilation(project).TypeResolveContext).Kind != TypeKind.Unknown; } + public ITestProject CreateTestProject(ITestSolution parentSolution, IProject project) + { + return new NUnitTestProject(project); + } + + /* + public ITestRunner CreateTestRunner() + { + return new NUnitTestRunner(); + } + + public ITestRunner CreateTestDebugger() + { + return new NUnitTestDebugger(); + } + + public bool IsTestMember(IMember member) { if (member == null || member.EntityType != EntityType.Method) @@ -72,6 +75,6 @@ namespace ICSharpCode.UnitTesting { var project = typeDefinition.ParentAssembly.GetProject(); return typeDefinition.Methods.Where(IsTestMember).Select(m => new TestMember(m.UnresolvedMember)); - } + }*/ } } diff --git a/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestProject.cs b/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestProject.cs new file mode 100644 index 0000000000..818eb2b65b --- /dev/null +++ b/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestProject.cs @@ -0,0 +1,26 @@ +// 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.Threading.Tasks; +using ICSharpCode.SharpDevelop.Gui; +using ICSharpCode.SharpDevelop.Project; + +namespace ICSharpCode.UnitTesting +{ + /// + /// NUnit test project. + /// + public class NUnitTestProject : TestProjectBase + { + public NUnitTestProject(IProject project) : base(project) + { + } + + public override Task RunTestsAsync(IEnumerable tests, TestExecutionOptions options, IProgressMonitor progressMonitor) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestRunner.cs b/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestRunner.cs index 24ddcca895..9d35baae13 100644 --- a/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestRunner.cs +++ b/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestRunner.cs @@ -2,7 +2,9 @@ // 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 ICSharpCode.SharpDevelop.Util; namespace ICSharpCode.UnitTesting @@ -23,7 +25,7 @@ namespace ICSharpCode.UnitTesting this.options = options; } - protected override ProcessStartInfo GetProcessStartInfo(SelectedTests selectedTests) + protected override ProcessStartInfo GetProcessStartInfo(IEnumerable selectedTests) { NUnitConsoleApplication app = new NUnitConsoleApplication(selectedTests, options); app.Results = base.TestResultsMonitor.FileName; diff --git a/src/AddIns/Analysis/UnitTesting/Pad/EmptyUnitTestsPad.cs b/src/AddIns/Analysis/UnitTesting/Pad/EmptyUnitTestsPad.cs deleted file mode 100644 index a268c37d6d..0000000000 --- a/src/AddIns/Analysis/UnitTesting/Pad/EmptyUnitTestsPad.cs +++ /dev/null @@ -1,53 +0,0 @@ -// 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.Linq; -using ICSharpCode.SharpDevelop.Project; - -namespace ICSharpCode.UnitTesting -{ - public class EmptyUnitTestsPad : IUnitTestsPad - { - Solution solution; - - public EmptyUnitTestsPad() - : this(null) - { - } - - public EmptyUnitTestsPad(Solution solution) - { - this.solution = solution; - } - - public void UpdateToolbar() - { - } - - public void BringToFront() - { - } - - public void ResetTestResults() - { - } - - public IProject[] GetProjects() - { - if (solution != null) { - return solution.Projects.ToArray(); - } - return new IProject[0]; - } - - public TestProject GetTestProject(IProject project) - { - return null; - } - - public void CollapseAll() - { - } - } -} diff --git a/src/AddIns/Analysis/UnitTesting/Pad/IUnitTestsPad.cs b/src/AddIns/Analysis/UnitTesting/Pad/IUnitTestsPad.cs deleted file mode 100644 index 0b94414a87..0000000000 --- a/src/AddIns/Analysis/UnitTesting/Pad/IUnitTestsPad.cs +++ /dev/null @@ -1,18 +0,0 @@ -// 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 ICSharpCode.SharpDevelop.Project; - -namespace ICSharpCode.UnitTesting -{ - public interface IUnitTestsPad - { - void UpdateToolbar(); - void BringToFront(); - void ResetTestResults(); - IProject[] GetProjects(); - TestProject GetTestProject(IProject project); - void CollapseAll(); - } -} diff --git a/src/AddIns/Analysis/UnitTesting/Pad/UnitTestsPad.cs b/src/AddIns/Analysis/UnitTesting/Pad/UnitTestsPad.cs index 56a2d7fb08..c06a55a7cd 100644 --- a/src/AddIns/Analysis/UnitTesting/Pad/UnitTestsPad.cs +++ b/src/AddIns/Analysis/UnitTesting/Pad/UnitTestsPad.cs @@ -19,23 +19,20 @@ namespace ICSharpCode.UnitTesting { public class UnitTestsPad : AbstractPadContent { - TestSolution testSolution; + ITestService testService; TestTreeView treeView; - bool disposed; DockPanel panel; ToolBar toolBar; List> pending = new List>(); - static UnitTestsPad instance; public UnitTestsPad() - : this(TestService.RegisteredTestFrameworks, TestService.Solution) + : this(SD.GetRequiredService()) { } - public UnitTestsPad(IRegisteredTestFrameworks testFrameworks, TestSolution testSolution) + public UnitTestsPad(ITestService testService) { - instance = this; - this.testSolution = testSolution; + this.testService = testService; panel = new DockPanel(); @@ -43,131 +40,28 @@ namespace ICSharpCode.UnitTesting panel.Children.Add(toolBar); DockPanel.SetDock(toolBar, Dock.Top); - treeView = new TestTreeView(testFrameworks, testSolution); + treeView = new TestTreeView(); panel.Children.Add(treeView); - // Add the load solution projects thread ended handler before - // we try to display the open solution so the event does not - // get missed. - SD.ParserService.LoadSolutionProjectsThread.Finished += LoadSolutionProjectsThreadFinished; - OnAddedLoadSolutionProjectsThreadEndedHandler(); - - ProjectService.SolutionClosed += SolutionClosed; - ProjectService.SolutionFolderRemoved += SolutionFolderRemoved; - ProjectService.ProjectAdded += ProjectAdded; - ProjectService.ProjectItemAdded += ProjectItemAdded; - ProjectService.ProjectItemRemoved += ProjectItemRemoved; - treeView.ContextMenu = CreateContextMenu("/SharpDevelop/Pads/UnitTestsPad/ContextMenu"); - } - - public static UnitTestsPad Instance { - get { return instance; } - } - - public override object Control { - get { return panel; } + + testService.OpenSolutionChanged += testService_OpenSolutionChanged; + testService_OpenSolutionChanged(null, null); } public override void Dispose() { - if (!disposed) { - disposed = true; - - ProjectService.ProjectItemRemoved -= ProjectItemRemoved; - ProjectService.ProjectItemAdded -= ProjectItemAdded; - ProjectService.ProjectAdded -= ProjectAdded; - ProjectService.SolutionFolderRemoved -= SolutionFolderRemoved; - ProjectService.SolutionClosed -= SolutionClosed; - SD.ParserService.LoadSolutionProjectsThread.Finished -= LoadSolutionProjectsThreadFinished; - } + testService.OpenSolutionChanged -= testService_OpenSolutionChanged; + base.Dispose(); } -// public TestTreeView TestTreeView { -// get { return treeView; } -// } - -// public void ResetTestResults() -// { -// treeView.ResetTestResults(); -// } -// - public IProject[] GetProjects() - { - return testSolution.TestableProjects.Select(tp => tp.Project).ToArray(); + public override object Control { + get { return panel; } } -// public TestProject GetTestProject(IProject project) -// { -// return treeView.GetTestProject(project); -// } - - /// - /// Updates the state of the buttons on the Unit Tests pad's - /// toolbar. - /// - public void UpdateToolbar() + void testService_OpenSolutionChanged(object sender, EventArgs e) { -// ToolbarService.UpdateToolbar(toolBar); - } - - /// - /// Collapses all nodes. - /// -// public void CollapseAll() -// { -// if (treeView == null || treeView.Nodes == null || treeView.Nodes.Count == 0) -// return; -// -// treeView.CollapseAll(); -// } - - /// - /// Called when a solution has been closed. - /// -// protected void SolutionClosed() -// { -// treeView.Clear(); -// } -// -// protected void SolutionFolderRemoved(ISolutionFolder solutionFolder) -// { -// treeView.RemoveSolutionFolder(solutionFolder); -// } - - /// - /// If the project item removed is a reference to a unit - /// test framework then the project will be removed from the - /// test tree. - /// -// protected void ProjectItemRemoved(ProjectItem projectItem) -// { -// treeView.ProjectItemRemoved(projectItem); -// } -// -// protected void ProjectItemAdded(ProjectItem projectItem) -// { -// treeView.ProjectItemAdded(projectItem); -// } - - /// - /// Protected method so we can test this method. - /// - protected void UpdateParseInfo(IUnresolvedFile oldFile, IUnresolvedFile newFile) - { - RootUnitTestNode root = (RootUnitTestNode)treeView.Root; - if (root == null) { -// SolutionLoaded(GetOpenSolution()); - root = (RootUnitTestNode)treeView.Root; - if (root == null) return; - } - var solution = GetOpenSolution(); - if (solution == null) - return; - var project = solution.FindProjectContainingFile((oldFile ?? newFile).FileName); - if (project == null) - return; - var projectNode = root.Children.OfType().FirstOrDefault(node => node.Project == project); + treeView.TestSolution = testService.OpenSolution; } /// @@ -187,107 +81,5 @@ namespace ICSharpCode.UnitTesting { return MenuService.CreateContextMenu(treeView, name); } - -// /// -// /// Virtual method so we can override this method and return -// /// a dummy TestTreeView when testing. -// /// -// protected virtual SharpTreeView CreateTestTreeView(IRegisteredTestFrameworks testFrameworks) -// { -// return new SharpTreeView(); -// } - - /// - /// Gets the currently open solution. - /// - protected virtual Solution GetOpenSolution() - { - return ProjectService.OpenSolution; - } - - /// - /// Determines whether the parser is currently still loading the - /// solution. - /// - protected virtual bool IsParserLoadingSolution { - get { return SD.ParserService.LoadSolutionProjectsThread.IsRunning; } - } - - /// - /// Indicates that an event handler for the ParserService's - /// LoadSolutionProjectsThreadEnded event has been added - /// - protected virtual void OnAddedLoadSolutionProjectsThreadEndedHandler() - { - } - - void SolutionClosed(object source, EventArgs e) - { -// SolutionClosed(); - UpdateToolbar(); - } - - void SolutionFolderRemoved(object source, SolutionFolderEventArgs e) - { -// SolutionFolderRemoved(e.SolutionFolder); - UpdateToolbar(); - } - - void ProjectAdded(object source, ProjectEventArgs e) - { - UpdateToolbar(); - } - - void LoadSolutionProjectsThreadFinished(object source, EventArgs e) - { - WorkbenchSingleton.SafeThreadAsyncCall(UpdateToolbar); - Solution solution = ProjectService.OpenSolution; -// WorkbenchSingleton.SafeThreadAsyncCall(SolutionLoaded, solution); - } - -// void TestTreeViewKeyPress(object source, KeyPressEventArgs e) -// { -// if (e.KeyChar == '\r') { -// e.Handled = true; -// GotoDefinition(); -// } else if (e.KeyChar == ' ') { -// e.Handled = true; -// RunTests(); -// } -// } - - void GotoDefinition() - { -// RunCommand(new GotoDefinitionCommand()); - } - - void RunTests() - { -// RunCommand(new RunTestInPadCommand()); - } - - void RunCommand(ICommand command) - { - command.Owner = treeView; - command.Run(); - } - - void ProjectItemAdded(object source, ProjectItemEventArgs e) - { -// ProjectItemAdded(e.ProjectItem); - } - - void ProjectItemRemoved(object source, ProjectItemEventArgs e) - { -// ProjectItemRemoved(e.ProjectItem); - } - - public void ResetTestResults() - { - foreach (var testProject in testSolution.TestableProjects) - testProject.ResetTestResults(); - } } - - } diff --git a/src/AddIns/Analysis/UnitTesting/TestRunner/ITestRunner.cs b/src/AddIns/Analysis/UnitTesting/TestRunner/ITestRunner.cs index f05df18e56..8a29324c5e 100644 --- a/src/AddIns/Analysis/UnitTesting/TestRunner/ITestRunner.cs +++ b/src/AddIns/Analysis/UnitTesting/TestRunner/ITestRunner.cs @@ -2,6 +2,7 @@ // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; +using System.Collections.Generic; using ICSharpCode.SharpDevelop.Util; namespace ICSharpCode.UnitTesting @@ -11,7 +12,7 @@ namespace ICSharpCode.UnitTesting event EventHandler TestFinished; event EventHandler AllTestsFinished; event EventHandler MessageReceived; - void Start(SelectedTests selectedTests); + void Start(IEnumerable selectedTests); void Stop(); } } diff --git a/src/AddIns/Analysis/UnitTesting/TestRunner/SelectedTests.cs b/src/AddIns/Analysis/UnitTesting/TestRunner/SelectedTests.cs deleted file mode 100644 index 8be2135988..0000000000 --- a/src/AddIns/Analysis/UnitTesting/TestRunner/SelectedTests.cs +++ /dev/null @@ -1,91 +0,0 @@ -// 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 ICSharpCode.NRefactory.TypeSystem; -using ICSharpCode.SharpDevelop.Project; - -namespace ICSharpCode.UnitTesting -{ - public class SelectedTests - { - string namespaceFilter; - TestClass c; - TestMember member; - List projects = new List(); - - public SelectedTests(IProject project, string namespaceFilter, TestClass c, TestMember member) - { - this.namespaceFilter = namespaceFilter; - this.c = c; - this.member = member; - - if (project != null) { - projects.Add(project); - } - } - - public SelectedTests(IProject project) - : this(project, null, null, null) - { - } - - public SelectedTests(object owner, IProject[] selectedProjects) - { - IProject project = TestableCondition.GetProject(owner); - if (project != null) { - projects.Add(project); - } else { - projects.AddRange(selectedProjects); - } - - TestProject testProject = TestService.Solution.GetTestProject(project); - if (testProject != null) { - member = testProject.GetTestMember(TestableCondition.GetMember(owner)); - c = testProject.GetTestClass(TestableCondition.GetClassFromMemberOrCaller(owner)); - } - namespaceFilter = TestableCondition.GetNamespace(owner); - } - - public bool HasProjects { - get { return projects.Count > 0; } - } - - public void RemoveFirstProject() - { - if (HasProjects) { - projects.RemoveAt(0); - } - } - - public IEnumerable Projects { - get { return projects; } - } - - public int ProjectsCount { - get {return projects.Count;} - } - - public IProject Project { - get { - if (projects.Count > 0) { - return projects[0]; - } - return null; - } - } - - public string NamespaceFilter { - get { return namespaceFilter; } - } - - public TestClass Class { - get { return c; } - } - - public TestMember Member { - get { return member; } - } - } -} diff --git a/src/AddIns/Analysis/UnitTesting/TestRunner/TestDebuggerBase.cs b/src/AddIns/Analysis/UnitTesting/TestRunner/TestDebuggerBase.cs index 933e6900ad..865aeabfeb 100644 --- a/src/AddIns/Analysis/UnitTesting/TestRunner/TestDebuggerBase.cs +++ b/src/AddIns/Analysis/UnitTesting/TestRunner/TestDebuggerBase.cs @@ -2,7 +2,9 @@ // 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 ICSharpCode.Core; using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop.Debugging; @@ -39,7 +41,7 @@ namespace ICSharpCode.UnitTesting get { return testResultsMonitor; } } - public override void Start(SelectedTests selectedTests) + public override void Start(IEnumerable selectedTests) { ProcessStartInfo startInfo = GetProcessStartInfo(selectedTests); if (IsDebuggerRunning) { diff --git a/src/AddIns/Analysis/UnitTesting/TestRunner/TestProcessRunnerBase.cs b/src/AddIns/Analysis/UnitTesting/TestRunner/TestProcessRunnerBase.cs index 6a8dd71d4e..71ded442a7 100644 --- a/src/AddIns/Analysis/UnitTesting/TestRunner/TestProcessRunnerBase.cs +++ b/src/AddIns/Analysis/UnitTesting/TestRunner/TestProcessRunnerBase.cs @@ -2,7 +2,9 @@ // 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 ICSharpCode.Core; using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop.Util; @@ -62,7 +64,7 @@ namespace ICSharpCode.UnitTesting OnMessageReceived(e.Line); } - public override void Start(SelectedTests selectedTests) + public override void Start(IEnumerable selectedTests) { ProcessStartInfo startInfo = GetProcessStartInfo(selectedTests); Start(startInfo); diff --git a/src/AddIns/Analysis/UnitTesting/TestRunner/TestResultTask.cs b/src/AddIns/Analysis/UnitTesting/TestRunner/TestResultTask.cs index 6e0f9bdfc5..b9fed31b2b 100644 --- a/src/AddIns/Analysis/UnitTesting/TestRunner/TestResultTask.cs +++ b/src/AddIns/Analysis/UnitTesting/TestRunner/TestResultTask.cs @@ -16,7 +16,7 @@ namespace ICSharpCode.UnitTesting { } - public static SDTask Create(TestResult result, TestProject project) + public static SDTask Create(TestResult result, ITestProject project) { TaskType taskType = TaskType.Warning; FileLineReference lineRef = null; @@ -76,7 +76,7 @@ namespace ICSharpCode.UnitTesting /// Returns the location of the specified test member in the /// project being tested. /// - static FileLineReference FindTest(string memberName, TestProject testProject) + static FileLineReference FindTest(string memberName, ITestProject testProject) { // if (testProject != null) { // TestMember testMember = testProject.TestClasses.GetTestMember(memberName); diff --git a/src/AddIns/Analysis/UnitTesting/TestRunner/TestRunnerBase.cs b/src/AddIns/Analysis/UnitTesting/TestRunner/TestRunnerBase.cs index e3482f992d..1ee17d65d2 100644 --- a/src/AddIns/Analysis/UnitTesting/TestRunner/TestRunnerBase.cs +++ b/src/AddIns/Analysis/UnitTesting/TestRunner/TestRunnerBase.cs @@ -2,6 +2,7 @@ // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; +using System.Collections.Generic; using System.Diagnostics; namespace ICSharpCode.UnitTesting @@ -12,7 +13,7 @@ namespace ICSharpCode.UnitTesting { } - protected virtual ProcessStartInfo GetProcessStartInfo(SelectedTests selectedTests) + protected virtual ProcessStartInfo GetProcessStartInfo(IEnumerable selectedTests) { return new ProcessStartInfo(); } @@ -63,6 +64,6 @@ namespace ICSharpCode.UnitTesting public abstract void Dispose(); public abstract void Stop(); - public abstract void Start(SelectedTests selectedTests); + public abstract void Start(IEnumerable selectedTests); } } diff --git a/src/AddIns/Analysis/UnitTesting/TreeView/ClassUnitTestNode.cs b/src/AddIns/Analysis/UnitTesting/TreeView/ClassUnitTestNode.cs index 0c6e97a26a..68df61d772 100644 --- a/src/AddIns/Analysis/UnitTesting/TreeView/ClassUnitTestNode.cs +++ b/src/AddIns/Analysis/UnitTesting/TreeView/ClassUnitTestNode.cs @@ -12,7 +12,7 @@ namespace ICSharpCode.UnitTesting /// /// Represents a TestClass in the tree view. /// - public class ClassUnitTestNode : UnitTestBaseNode + public class ClassUnitTestNode : UnitTestNode { TestClass testClass; diff --git a/src/AddIns/Analysis/UnitTesting/TreeView/ITestTreeView.cs b/src/AddIns/Analysis/UnitTesting/TreeView/ITestTreeView.cs index d7e65035ce..80601d33a3 100644 --- a/src/AddIns/Analysis/UnitTesting/TreeView/ITestTreeView.cs +++ b/src/AddIns/Analysis/UnitTesting/TreeView/ITestTreeView.cs @@ -2,6 +2,7 @@ // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) +using System.Collections.Generic; using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.SharpDevelop.Project; using System; @@ -11,24 +12,13 @@ namespace ICSharpCode.UnitTesting public interface ITestTreeView { /// - /// Gets the selected member in the test tree view. + /// Gets the test solution that is being displayed in this tree view. /// - TestMember SelectedMember {get;} + ITestSolution TestSolution { get; } /// - /// Gets the selected class in the test tree view. + /// Gets the selected tests. /// - TestClass SelectedClass {get;} - - /// - /// Gets the selected project for the selected node - /// in the test tree view. - /// - TestProject SelectedProject {get;} - - /// - /// Gets the namespace for the selected namespace node. - /// - string SelectedNamespace {get;} + IEnumerable SelectedTests { get; } } } diff --git a/src/AddIns/Analysis/UnitTesting/TreeView/MemberUnitTestNode.cs b/src/AddIns/Analysis/UnitTesting/TreeView/MemberUnitTestNode.cs index 14d943e0a5..80b1aadeb1 100644 --- a/src/AddIns/Analysis/UnitTesting/TreeView/MemberUnitTestNode.cs +++ b/src/AddIns/Analysis/UnitTesting/TreeView/MemberUnitTestNode.cs @@ -10,7 +10,7 @@ using ICSharpCode.TreeView; namespace ICSharpCode.UnitTesting { - public class MemberUnitTestNode : UnitTestBaseNode + public class MemberUnitTestNode : UnitTestNode { TestMember testMember; diff --git a/src/AddIns/Analysis/UnitTesting/TreeView/NamespaceUnitTestNode.cs b/src/AddIns/Analysis/UnitTesting/TreeView/NamespaceUnitTestNode.cs index 14af8158d0..cf8e846981 100644 --- a/src/AddIns/Analysis/UnitTesting/TreeView/NamespaceUnitTestNode.cs +++ b/src/AddIns/Analysis/UnitTesting/TreeView/NamespaceUnitTestNode.cs @@ -16,7 +16,7 @@ using ICSharpCode.TreeView; namespace ICSharpCode.UnitTesting { - public class NamespaceUnitTestNode : UnitTestBaseNode + public class NamespaceUnitTestNode : UnitTestNode { readonly string shortName; readonly string namespaceName; @@ -50,11 +50,11 @@ namespace ICSharpCode.UnitTesting internal override TestResultType TestResultType { get { if (Children.Count == 0) return TestResultType.None; - if (Children.OfType().Any(node => node.TestResultType == TestResultType.Failure)) + if (Children.OfType().Any(node => node.TestResultType == TestResultType.Failure)) return TestResultType.Failure; - if (Children.OfType().Any(node => node.TestResultType == TestResultType.None)) + if (Children.OfType().Any(node => node.TestResultType == TestResultType.None)) return TestResultType.None; - if (Children.OfType().Any(node => node.TestResultType == TestResultType.Ignored)) + if (Children.OfType().Any(node => node.TestResultType == TestResultType.Ignored)) return TestResultType.Ignored; return TestResultType.Success; } diff --git a/src/AddIns/Analysis/UnitTesting/TreeView/ProjectUnitTestNode.cs b/src/AddIns/Analysis/UnitTesting/TreeView/ProjectUnitTestNode.cs index ab64f8bd94..c5b073843f 100644 --- a/src/AddIns/Analysis/UnitTesting/TreeView/ProjectUnitTestNode.cs +++ b/src/AddIns/Analysis/UnitTesting/TreeView/ProjectUnitTestNode.cs @@ -17,7 +17,7 @@ using ICSharpCode.TreeView; namespace ICSharpCode.UnitTesting { - public class ProjectUnitTestNode : UnitTestBaseNode + public class ProjectUnitTestNode : UnitTestNode { TestProject project; @@ -47,7 +47,7 @@ namespace ICSharpCode.UnitTesting case NotifyCollectionChangedAction.Add: foreach (TestClass c in e.NewItems) { if (c.Namespace == "") continue; - UnitTestBaseNode node = FindOrCreateNamespace(this, project.Project.RootNamespace, c.Namespace); + UnitTestNode node = FindOrCreateNamespace(this, project.Project.RootNamespace, c.Namespace); node.Children.OrderedInsert(new ClassUnitTestNode(c), NodeTextComparer); } break; @@ -70,7 +70,7 @@ namespace ICSharpCode.UnitTesting } } - static UnitTestBaseNode FindOrCreateNamespace(UnitTestBaseNode parent, string parentNamespace, string @namespace) + static UnitTestNode FindOrCreateNamespace(UnitTestNode parent, string parentNamespace, string @namespace) { if (parentNamespace == @namespace) return parent; @@ -101,7 +101,7 @@ namespace ICSharpCode.UnitTesting } } - static UnitTestBaseNode FindNamespace(UnitTestBaseNode parent, string parentNamespace, string @namespace) + static UnitTestNode FindNamespace(UnitTestNode parent, string parentNamespace, string @namespace) { if (parentNamespace == @namespace) return parent; @@ -120,7 +120,7 @@ namespace ICSharpCode.UnitTesting Children.Clear(); foreach (var g in project.TestClasses.Select(c => new ClassUnitTestNode(c)).GroupBy(tc => tc.TestClass.Namespace)) { if (g.Key == "") continue; - UnitTestBaseNode node = FindOrCreateNamespace(this, project.Project.RootNamespace, g.Key); + UnitTestNode node = FindOrCreateNamespace(this, project.Project.RootNamespace, g.Key); node.Children.AddRange(g.OrderBy(NodeTextComparer)); } } diff --git a/src/AddIns/Analysis/UnitTesting/TreeView/RootUnitTestNode.cs b/src/AddIns/Analysis/UnitTesting/TreeView/RootUnitTestNode.cs index 53ab00bb32..b300d52c2a 100644 --- a/src/AddIns/Analysis/UnitTesting/TreeView/RootUnitTestNode.cs +++ b/src/AddIns/Analysis/UnitTesting/TreeView/RootUnitTestNode.cs @@ -24,7 +24,7 @@ namespace ICSharpCode.UnitTesting /// /// Description of RootUnitTestNode. /// - public class RootUnitTestNode : UnitTestBaseNode + public class RootUnitTestNode : UnitTestNode { readonly TestSolution testSolution; @@ -63,11 +63,11 @@ namespace ICSharpCode.UnitTesting internal override TestResultType TestResultType { get { if (Children.Count == 0) return TestResultType.None; - if (Children.OfType().Any(node => node.TestResultType == TestResultType.Failure)) + if (Children.OfType().Any(node => node.TestResultType == TestResultType.Failure)) return TestResultType.Failure; - if (Children.OfType().Any(node => node.TestResultType == TestResultType.None)) + if (Children.OfType().Any(node => node.TestResultType == TestResultType.None)) return TestResultType.None; - if (Children.OfType().Any(node => node.TestResultType == TestResultType.Ignored)) + if (Children.OfType().Any(node => node.TestResultType == TestResultType.Ignored)) return TestResultType.Ignored; return TestResultType.Success; } diff --git a/src/AddIns/Analysis/UnitTesting/TreeView/TestTreeView.cs b/src/AddIns/Analysis/UnitTesting/TreeView/TestTreeView.cs index 374d6b346e..34a1a77c53 100644 --- a/src/AddIns/Analysis/UnitTesting/TreeView/TestTreeView.cs +++ b/src/AddIns/Analysis/UnitTesting/TreeView/TestTreeView.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using ICSharpCode.Core; using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.SharpDevelop; @@ -23,100 +24,49 @@ namespace ICSharpCode.UnitTesting [Flags] public enum TestTreeViewState { None = 0, - SourceCodeItemSelected = 1 + SupportsGoToDefinition = 1 } - IRegisteredTestFrameworks testFrameworks; + ITestSolution testSolution; - public TestTreeView(IRegisteredTestFrameworks testFrameworks, TestSolution testSolution) - { - this.testFrameworks = testFrameworks; - // Show 'All Tests' root node only if there is more than one test project in the solution - testSolution.TestableProjects.CollectionChanged += delegate { ShowRoot = testSolution.TestableProjects.Count > 1; }; - this.Root = new RootUnitTestNode(testSolution); - } - - /// - /// Gets the current state of the test tree view. - /// - public Enum InternalState { - get { - if (SelectedItem is ClassUnitTestNode || SelectedItem is MemberUnitTestNode) - return TestTreeViewState.SourceCodeItemSelected; - return TestTreeViewState.None; - } - } - - bool IsTestProject(IProject project) - { - return testFrameworks.IsTestProject(project); - } - - /// - /// Gets the member of the currently selected tree node. - /// - public TestMember SelectedMember { - get { - MemberUnitTestNode memberNode = SelectedItem as MemberUnitTestNode; - if (memberNode != null) { - return memberNode.TestMember; - } - return null; - } - } - - /// - /// Gets the class of the currently selected tree node. - /// - public TestClass SelectedClass { - get { - ClassUnitTestNode classNode = SelectedItem as ClassUnitTestNode; - if (classNode == null) { - classNode = GetClassNodeFromSelectedMemberNode(); + public ITestSolution TestSolution { + get { return testSolution; } + set { + if (testSolution == value) + return; + testSolution = value; + if (testSolution != null) { + this.Root = new UnitTestNode(testSolution); + this.Root.Children.CollectionChanged += delegate { + this.ShowRoot = this.Root != null && this.Root.Children.Count > 1; + }; + } else { + this.Root = null; } - - if (classNode != null) { - return classNode.TestClass; - } - return null; } } - ClassUnitTestNode GetClassNodeFromSelectedMemberNode() + public TestTreeView() { - MemberUnitTestNode memberNode = SelectedItem as MemberUnitTestNode; - if (memberNode != null) { - return memberNode.Parent as ClassUnitTestNode; - } - return null; + this.ShowRoot = false; } /// - /// If a namespace node is selected then the fully qualified namespace - /// for this node is returned (i.e. includes the parent namespace prefixed - /// to it). For all other nodes this returns null. + /// Gets the current state of the test tree view. /// - public string SelectedNamespace { + public Enum InternalState { get { - NamespaceUnitTestNode selectedNode = SelectedItem as NamespaceUnitTestNode; - if (selectedNode != null) { - return selectedNode.NamespaceName; - } - return null; + var node = SelectedItem as UnitTestNode; + if (node != null && node.Test.SupportsGoToDefinition) + return TestTreeViewState.SupportsGoToDefinition; + else + return TestTreeViewState.None; } } - /// - /// Gets the selected test project. - /// - public TestProject SelectedProject { + public IEnumerable SelectedTests { get { - for (var node = SelectedItem as SharpTreeNode; node != null; node = node.Parent) { - var projectNode = node as ProjectUnitTestNode; - if (projectNode != null) - return projectNode.Project; - } - return null; + return this.GetTopLevelSelection().OfType().Select(n => n.Test); } } } diff --git a/src/AddIns/Analysis/UnitTesting/TreeView/UnitTestBaseNode.cs b/src/AddIns/Analysis/UnitTesting/TreeView/UnitTestBaseNode.cs deleted file mode 100644 index 98106fa155..0000000000 --- a/src/AddIns/Analysis/UnitTesting/TreeView/UnitTestBaseNode.cs +++ /dev/null @@ -1,38 +0,0 @@ -// 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.Linq; -using ICSharpCode.NRefactory.Utils; -using ICSharpCode.TreeView; - -namespace ICSharpCode.UnitTesting -{ - public abstract class UnitTestBaseNode : SharpTreeNode - { - protected static readonly KeyComparer NodeTextComparer = KeyComparer.Create((SharpTreeNode n) => n.Text.ToString(), StringComparer.OrdinalIgnoreCase, StringComparer.OrdinalIgnoreCase); - - internal abstract TestResultType TestResultType { get; } - - public override object Icon { - get { return GetIcon(TestResultType); } - } - - object GetIcon(TestResultType testResultType) - { - switch (testResultType) { - case TestResultType.None: - return Images.Grey; - case TestResultType.Success: - return Images.Green; - case TestResultType.Failure: - return Images.Red; - case TestResultType.Ignored: - return Images.Yellow; - default: - throw new Exception("Invalid value for TestResultType"); - } - } - } -} diff --git a/src/AddIns/Analysis/UnitTesting/TreeView/UnitTestNode.cs b/src/AddIns/Analysis/UnitTesting/TreeView/UnitTestNode.cs new file mode 100644 index 0000000000..df565a586d --- /dev/null +++ b/src/AddIns/Analysis/UnitTesting/TreeView/UnitTestNode.cs @@ -0,0 +1,128 @@ +// 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.Collections.Specialized; +using System.Linq; + +using ICSharpCode.NRefactory.Utils; +using ICSharpCode.SharpDevelop; +using ICSharpCode.TreeView; + +namespace ICSharpCode.UnitTesting +{ + public class UnitTestNode : SharpTreeNode + { + protected static readonly IComparer NodeTextComparer = KeyComparer.Create((SharpTreeNode n) => n.Text.ToString(), StringComparer.OrdinalIgnoreCase, StringComparer.OrdinalIgnoreCase); + + readonly ITest test; + + public UnitTestNode(ITest test) + { + if (test == null) + throw new ArgumentNullException("test"); + this.test = test; + test.DisplayNameChanged += test_NameChanged; + test.ResultChanged += test_ResultChanged; + LazyLoading = true; + } + + void DetachEventHandlers() + { + test.DisplayNameChanged -= test_NameChanged; + test.ResultChanged -= test_ResultChanged; + // If children loaded, also detach the collection change event handler + if (!LazyLoading) { + test.NestedTests.CollectionChanged -= test_NestedTests_CollectionChanged; + } + } + + public ITest Test { + get { return test; } + } + + #region Manage Children + protected override void LoadChildren() + { + Children.Clear(); + InsertNestedTests(test.NestedTests); + test.NestedTests.CollectionChanged += test_NestedTests_CollectionChanged; + } + + void InsertNestedTests(IEnumerable nestedTests) + { + foreach (var test in nestedTests) { + Children.OrderedInsert(test.CreateTreeNode(), NodeTextComparer); + } + } + + void test_NestedTests_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (!IsVisible) { + SwitchBackToLazyLoading(); + return; + } + switch (e.Action) { + case NotifyCollectionChangedAction.Add: + InsertNestedTests(e.NewItems.Cast()); + break; + case NotifyCollectionChangedAction.Remove: + Children.RemoveAll(n => e.OldItems.Contains(((UnitTestNode)n).Test)); + break; + case NotifyCollectionChangedAction.Reset: + if (IsExpanded) { + Children.Clear(); + InsertNestedTests(test.NestedTests); + } else { + SwitchBackToLazyLoading(); + } + break; + default: + throw new NotSupportedException("Invalid value for NotifyCollectionChangedAction"); + } + } + + void SwitchBackToLazyLoading() + { + test.NestedTests.CollectionChanged -= test_NestedTests_CollectionChanged; + Children.Clear(); + LazyLoading = true; + } + #endregion + + #region Icon + Text + public override object Icon { + get { + switch (test.Result) { + case TestResultType.None: + return Images.Grey; + case TestResultType.Success: + return Images.Green; + case TestResultType.Failure: + return Images.Red; + case TestResultType.Ignored: + return Images.Yellow; + default: + throw new NotSupportedException("Invalid value for TestResultType"); + } + } + } + + void test_ResultChanged(object sender, EventArgs e) + { + RaisePropertyChanged("Icon"); + RaisePropertyChanged("ExpandedIcon"); + } + + public override object Text { + get { return test.DisplayName; } + } + + void test_NameChanged(object sender, EventArgs e) + { + RaisePropertyChanged("Text"); + } + #endregion + } +} diff --git a/src/AddIns/Analysis/UnitTesting/UnitTesting.addin b/src/AddIns/Analysis/UnitTesting/UnitTesting.addin index 9233f2c201..41649d8fd9 100644 --- a/src/AddIns/Analysis/UnitTesting/UnitTesting.addin +++ b/src/AddIns/Analysis/UnitTesting/UnitTesting.addin @@ -17,6 +17,11 @@ + + + + + class="ICSharpCode.UnitTesting.RunSelectedTestCommand"/> @@ -63,7 +68,7 @@ + class="ICSharpCode.UnitTesting.RunSelectedTestWithDebuggerCommand"/> @@ -78,12 +83,12 @@ + class="ICSharpCode.UnitTesting.RunAllTestsInSolutionCommand"/> + class="ICSharpCode.UnitTesting.RunSelectedTestCommand"/> @@ -120,7 +125,7 @@ + class="ICSharpCode.UnitTesting.RunSelectedTestCommand"/> @@ -133,7 +138,7 @@ label="${res:NUnitPad.NUnitPadContent.StopTests}" class="ICSharpCode.UnitTesting.StopTestsCommand"/> - + @@ -152,7 +157,7 @@ + class="ICSharpCode.UnitTesting.RunAllTestsInSolutionCommand"/> @@ -165,7 +170,7 @@ + class="ICSharpCode.UnitTesting.RunAllTestsInProjectCommand"/> diff --git a/src/AddIns/Analysis/UnitTesting/UnitTesting.csproj b/src/AddIns/Analysis/UnitTesting/UnitTesting.csproj index 62e267adb5..f2c713e547 100644 --- a/src/AddIns/Analysis/UnitTesting/UnitTesting.csproj +++ b/src/AddIns/Analysis/UnitTesting/UnitTesting.csproj @@ -66,79 +66,54 @@ - - - - + - - - - + + + - - - - - + + + + - - + + + - - + + - - - - - - - - - + - - - - - + - - UnitTestingOptionsPanel.xaml - - - - - - - Never @@ -197,7 +172,6 @@ - diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/Utils/MultiDictionary.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/Utils/MultiDictionary.cs index 496b66ba9e..1eab0c73ab 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/Utils/MultiDictionary.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/Utils/MultiDictionary.cs @@ -62,6 +62,15 @@ namespace ICSharpCode.NRefactory.Utils return false; } + /// + /// Removes all entries with the specified key. + /// + /// Returns true if at least one entry was removed. + public bool RemoveAll(TKey key) + { + return dict.Remove(key); + } + public void Clear() { dict.Clear(); @@ -81,10 +90,21 @@ namespace ICSharpCode.NRefactory.Utils } } + /// + /// Returns the number of different keys. + /// public int Count { get { return dict.Count; } } + public ICollection Keys { + get { return dict.Keys; } + } + + public IEnumerable Values { + get { return dict.Values.SelectMany(list => list); } + } + IEnumerable ILookup.this[TKey key] { get { return this[key]; } } diff --git a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj index 329c26e2e9..ffa7493b77 100644 --- a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj +++ b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj @@ -372,6 +372,7 @@ + diff --git a/src/Main/Base/Project/Src/Commands/BuildCommands.cs b/src/Main/Base/Project/Src/Commands/BuildCommands.cs index 87f009699d..5ed48d0acb 100644 --- a/src/Main/Base/Project/Src/Commands/BuildCommands.cs +++ b/src/Main/Base/Project/Src/Commands/BuildCommands.cs @@ -3,9 +3,10 @@ using System; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; - using ICSharpCode.Core; using ICSharpCode.Core.Presentation; using ICSharpCode.SharpDevelop.Debugging; @@ -76,6 +77,26 @@ namespace ICSharpCode.SharpDevelop.Project.Commands } } + public Task BuildAsync(CancellationToken cancellationToken) + { + var registration = cancellationToken.Register(BuildEngine.CancelGuiBuild, true); + var tcs = new TaskCompletionSource(); + this.BuildComplete += delegate { + registration.Dispose(); + if (cancellationToken.IsCancellationRequested) + tcs.TrySetCanceled(); + else + tcs.TrySetResult(this.LastBuildResults); + }; + try { + StartBuild(); + } catch (Exception ex) { + registration.Dispose(); + tcs.TrySetException(ex); + } + return tcs.Task; + } + /// /// Notifies the user that #develp's internal MSBuildEngine /// implementation only supports compiling solutions and projects; diff --git a/src/AddIns/Analysis/UnitTesting/Commands/MultipleProjectBuildable.cs b/src/Main/Base/Project/Src/Project/MultipleProjectBuildable.cs similarity index 92% rename from src/AddIns/Analysis/UnitTesting/Commands/MultipleProjectBuildable.cs rename to src/Main/Base/Project/Src/Project/MultipleProjectBuildable.cs index 28605c0303..ec732783bd 100644 --- a/src/AddIns/Analysis/UnitTesting/Commands/MultipleProjectBuildable.cs +++ b/src/Main/Base/Project/Src/Project/MultipleProjectBuildable.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Linq; using ICSharpCode.SharpDevelop.Project; -namespace ICSharpCode.UnitTesting +namespace ICSharpCode.SharpDevelop.Project { /// /// IBuildable implementation that builds several projects. @@ -17,7 +17,7 @@ namespace ICSharpCode.UnitTesting public MultipleProjectBuildable(IEnumerable projects) { - this.projects = projects.ToArray(); + this.projects = projects.Where(p => p != null).ToArray(); } public string Name { diff --git a/src/Main/Base/Project/Src/Services/Tasks/TaskService.cs b/src/Main/Base/Project/Src/Services/Tasks/TaskService.cs index 0ccae06d2a..379d74f63c 100644 --- a/src/Main/Base/Project/Src/Services/Tasks/TaskService.cs +++ b/src/Main/Base/Project/Src/Services/Tasks/TaskService.cs @@ -110,10 +110,16 @@ namespace ICSharpCode.SharpDevelop public static void ClearExceptCommentTasks() { - List commentTasks = new List(CommentTasks); - Clear(); - foreach (SDTask t in commentTasks) { - Add(t); + bool wasInUpdate = InUpdate; + InUpdate = true; + try { + List commentTasks = new List(CommentTasks); + Clear(); + foreach (SDTask t in commentTasks) { + Add(t); + } + } finally { + InUpdate = wasInUpdate; } } @@ -149,10 +155,10 @@ namespace ICSharpCode.SharpDevelop List newTasks = new List(); foreach (TagComment tag in tagComments) { newTasks.Add(new SDTask(fileName, - tag.Key + tag.CommentString, - tag.Region.BeginColumn, - tag.Region.BeginLine, - TaskType.Comment)); + tag.Key + tag.CommentString, + tag.Region.BeginColumn, + tag.Region.BeginLine, + TaskType.Comment)); } List oldTasks = new List();