2
// UnitTestTextEditorExtension.cs
5
// Mike Krüger <mkrueger@xamarin.com>
7
// Copyright (c) 2013 Xamarin Inc. (http://xamarin.com)
9
// Permission is hereby granted, free of charge, to any person obtaining a copy
10
// of this software and associated documentation files (the "Software"), to deal
11
// in the Software without restriction, including without limitation the rights
12
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
// copies of the Software, and to permit persons to whom the Software is
14
// furnished to do so, subject to the following conditions:
16
// The above copyright notice and this permission notice shall be included in
17
// all copies or substantial portions of the Software.
19
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27
using MonoDevelop.Ide.Gui.Content;
28
using ICSharpCode.NRefactory.CSharp;
29
using MonoDevelop.Refactoring;
30
using ICSharpCode.NRefactory.CSharp.Resolver;
31
using System.Collections.Generic;
32
using System.Threading.Tasks;
33
using System.Threading;
35
using Mono.TextEditor;
36
using MonoDevelop.NUnit;
37
using MonoDevelop.Core;
38
using MonoDevelop.Ide.Tasks;
39
using MonoDevelop.Ide;
40
using MonoDevelop.Components.Docking;
41
using ICSharpCode.NRefactory.Semantics;
42
using ICSharpCode.NRefactory.TypeSystem;
43
using MonoDevelop.Components.Commands;
47
using MonoDevelop.AnalysisCore;
49
namespace MonoDevelop.CSharp
51
class UnitTestTextEditorExtension : TextEditorExtension
53
public override void Initialize ()
56
Document.DocumentParsed += HandleDocumentParsed;
59
public override void Dispose ()
62
Document.DocumentParsed -= HandleDocumentParsed;
66
CancellationTokenSource src = new CancellationTokenSource ();
68
void HandleDocumentParsed (object sender, EventArgs e)
70
if (!AnalysisOptions.EnableUnitTestEditorIntegration)
73
src = new CancellationTokenSource ();
74
var token = src.Token;
75
ThreadPool.QueueUserWorkItem (delegate {
76
var resolver = document.GetSharedResolver ();
77
if (resolver.Result == null)
79
var visitor = new NUnitVisitor (resolver.Result);
81
visitor.VisitSyntaxTree (document.ParsedDocument.GetAst<SyntaxTree> ());
82
} catch (Exception ex) {
83
LoggingService.LogError ("Exception while analyzing ast for unit tests.", ex);
86
if (token.IsCancellationRequested)
88
Application.Invoke (delegate {
89
if (document.Editor.Parent.ActionMargin.IsVisible ^ (visitor.FoundTests.Count > 0))
90
document.Editor.Parent.QueueDraw ();
91
document.Editor.Parent.ActionMargin.IsVisible = visitor.FoundTests.Count > 0;
93
foreach (var oldMarker in currentMarker)
94
document.Editor.Document.RemoveMarker (oldMarker);
96
foreach (var foundTest in visitor.FoundTests) {
97
if (token.IsCancellationRequested)
99
var unitTestMarker = new UnitTestMarker (foundTest, document);
100
currentMarker.Add (unitTestMarker);
101
document.Editor.Document.AddMarker (foundTest.LineNumber, unitTestMarker);
108
static uint timeoutHandler;
110
static void RemoveHandler ()
112
if (timeoutHandler != 0) {
113
GLib.Source.Remove (timeoutHandler);
118
List<UnitTestMarker> currentMarker = new List<UnitTestMarker>();
120
class UnitTestMarker : MarginMarker
122
readonly NUnitVisitor.UnitTest unitTest;
123
readonly MonoDevelop.Ide.Gui.Document doc;
125
public UnitTestMarker(NUnitVisitor.UnitTest unitTest, MonoDevelop.Ide.Gui.Document doc)
127
this.unitTest = unitTest;
131
public override bool CanDrawForeground (Margin margin)
133
return margin is ActionMargin;
136
public override void InformMouseHover (TextEditor editor, Margin margin, MarginMouseEventArgs args)
139
if (unitTest.IsFixture) {
141
toolTip = GettextCatalog.GetString ("NUnit Fixture failed (click to run)");
142
if (!string.IsNullOrEmpty (failMessage))
143
toolTip += Environment.NewLine + failMessage.TrimEnd ();
145
toolTip = GettextCatalog.GetString ("NUnit Fixture (click to run)");
149
toolTip = GettextCatalog.GetString ("NUnit Test failed (click to run)");
150
if (!string.IsNullOrEmpty (failMessage))
151
toolTip += Environment.NewLine + failMessage.TrimEnd ();
152
foreach (var id in unitTest.TestCases) {
153
var test = NUnitService.Instance.SearchTestById (unitTest.UnitTestIdentifier + id);
155
var result = test.GetLastResult ();
156
if (result.IsFailure) {
157
if (!string.IsNullOrEmpty (result.Message)) {
158
toolTip += Environment.NewLine + "Test" + id +":";
159
toolTip += Environment.NewLine + result.Message.TrimEnd ();
166
toolTip = GettextCatalog.GetString ("NUnit Test (click to run)");
170
editor.TooltipText = toolTip;
173
static Gtk.Menu menu;
175
public override void InformMousePress (TextEditor editor, Margin margin, MarginMouseEventArgs args)
180
var debugModeSet = Runtime.ProcessService.GetDebugExecutionMode ();
182
menu = new Gtk.Menu ();
183
if (unitTest.IsFixture) {
184
var menuItem = new Gtk.MenuItem ("_Run All");
185
menuItem.Activated += new TestRunner (doc, unitTest.UnitTestIdentifier, false).Run;
187
if (debugModeSet != null) {
188
menuItem = new Gtk.MenuItem ("_Debug All");
189
menuItem.Activated += new TestRunner (doc, unitTest.UnitTestIdentifier, true).Run;
193
if (unitTest.TestCases.Count == 0) {
194
var menuItem = new Gtk.MenuItem ("_Run");
195
menuItem.Activated += new TestRunner (doc, unitTest.UnitTestIdentifier, false).Run;
197
if (debugModeSet != null) {
198
menuItem = new Gtk.MenuItem ("_Debug");
199
menuItem.Activated += new TestRunner (doc, unitTest.UnitTestIdentifier, true).Run;
203
var menuItem = new Gtk.MenuItem ("_Run All");
204
menuItem.Activated += new TestRunner (doc, unitTest.UnitTestIdentifier, false).Run;
206
if (debugModeSet != null) {
207
menuItem = new Gtk.MenuItem ("_Debug All");
208
menuItem.Activated += new TestRunner (doc, unitTest.UnitTestIdentifier, true).Run;
211
menu.Add (new Gtk.SeparatorMenuItem ());
212
foreach (var id in unitTest.TestCases) {
213
var submenu = new Gtk.Menu ();
214
menuItem = new Gtk.MenuItem ("_Run");
215
menuItem.Activated += new TestRunner (doc, unitTest.UnitTestIdentifier + id, false).Run;
216
submenu.Add (menuItem);
217
if (debugModeSet != null) {
218
menuItem = new Gtk.MenuItem ("_Debug");
219
menuItem.Activated += new TestRunner (doc, unitTest.UnitTestIdentifier + id, true).Run;
220
submenu.Add (menuItem);
223
var label = "Test" + id;
224
string tooltip = null;
225
var test = NUnitService.Instance.SearchTestById (unitTest.UnitTestIdentifier + id);
227
var result = test.GetLastResult ();
228
if (result != null && result.IsFailure) {
229
tooltip = result.Message;
234
var subMenuItem = new Gtk.MenuItem (label);
235
if (!string.IsNullOrEmpty (tooltip))
236
subMenuItem.TooltipText = tooltip;
237
subMenuItem.Submenu = submenu;
238
menu.Add (subMenuItem);
243
editor.TextArea.ResetMouseState ();
244
GtkWorkarounds.ShowContextMenu (menu, editor, new Gdk.Rectangle ((int)args.X, (int)args.Y, 1, 1));
249
readonly MonoDevelop.Ide.Gui.Document doc;
250
readonly string testCase;
253
public TestRunner (MonoDevelop.Ide.Gui.Document doc, string testCase, bool debug)
256
this.testCase = testCase;
260
bool TimeoutHandler ()
262
var test = NUnitService.Instance.SearchTestById (testCase);
273
internal void Run (object sender, EventArgs e)
277
if (IdeApp.ProjectOperations.IsBuilding (IdeApp.ProjectOperations.CurrentSelectedSolution) ||
278
IdeApp.ProjectOperations.IsRunning (IdeApp.ProjectOperations.CurrentSelectedSolution))
280
var buildOperation = IdeApp.ProjectOperations.Build (IdeApp.ProjectOperations.CurrentSelectedSolution);
281
buildOperation.Completed += delegate {
282
if (!buildOperation.Success)
285
timeoutHandler = GLib.Timeout.Add (200, TimeoutHandler);
289
void RunTest (UnitTest test)
291
NUnitService.ResetResult (test.RootTest);
292
var debugModeSet = Runtime.ProcessService.GetDebugExecutionMode ();
293
MonoDevelop.Core.Execution.IExecutionHandler ctx = null;
294
if (debug && debugModeSet != null) {
295
foreach (var executionMode in debugModeSet.ExecutionModes) {
296
if (test.CanRun (executionMode.ExecutionHandler)) {
297
ctx = executionMode.ExecutionHandler;
302
NUnitService.Instance.RunTest (test, ctx).Completed += delegate {
303
Application.Invoke (delegate {
304
doc.Editor.Parent.QueueDraw ();
314
public override void DrawForeground (TextEditor editor, Cairo.Context cr, MarginDrawMetrics metrics)
316
cr.Arc (metrics.X + metrics.Width / 2 + 2, metrics.Y + metrics.Height / 2, 7 * editor.Options.Zoom, 0, Math.PI * 2);
318
var test = NUnitService.Instance.SearchTestById (unitTest.UnitTestIdentifier);
319
bool searchCases = false;
321
var result = test.GetLastResult ();
322
if (result == null || result.IsNotRun) {
323
cr.Color = new Cairo.Color (0.5, 0.5, 0.5);
325
} else if (result.IsSuccess) {
326
cr.Color = new Cairo.Color (0, 1, 0, test.IsHistoricResult ? 0.2 : 1.0);
327
} else if (result.IsFailure) {
328
cr.Color = new Cairo.Color (1, 0, 0, test.IsHistoricResult ? 0.2 : 1.0);
329
failMessage = result.Message;
331
} else if (result.IsInconclusive) {
332
cr.Color = new Cairo.Color (0, 1, 1, test.IsHistoricResult ? 0.2 : 1.0);
335
cr.Color = new Cairo.Color (0.5, 0.5, 0.5);
340
foreach (var caseId in unitTest.TestCases) {
341
test = NUnitService.Instance.SearchTestById (unitTest.UnitTestIdentifier + caseId);
343
var result = test.GetLastResult ();
344
if (result == null || result.IsNotRun || test.IsHistoricResult) {
345
} else if (result.IsSuccess) {
346
cr.Color = new Cairo.Color (0, 1, 0);
347
} else if (result.IsFailure) {
348
cr.Color = new Cairo.Color (1, 0, 0);
349
failMessage = result.Message;
352
} else if (result.IsInconclusive) {
353
cr.Color = new Cairo.Color (0, 1, 1);
362
var result = test.GetLastResult ();
363
if (result == null || result.IsNotRun) {
364
cr.Color = new Cairo.Color (0.2, 0.2, 0.2);
366
} else if (result.IsSuccess && !test.IsHistoricResult) {
367
cr.Color = new Cairo.Color (0, 0.5, 0);
369
} else if (result.IsFailure && !test.IsHistoricResult) {
370
cr.Color = new Cairo.Color (0.5, 0, 0);
372
} else if (result.IsInconclusive && !test.IsHistoricResult) {
373
cr.Color = new Cairo.Color (0, 0.7, 0.7);
381
class NUnitVisitor : DepthFirstAstVisitor
383
readonly CSharpAstResolver resolver;
384
List<UnitTest> foundTests = new List<UnitTest> ();
386
public IList<UnitTest> FoundTests {
392
public class UnitTest
394
public int LineNumber { get; set; }
395
public bool IsFixture { get; set; }
396
public string UnitTestIdentifier { get; set; }
397
public List<string> TestCases = new List<string> ();
399
public UnitTest (int lineNumber)
401
this.LineNumber = lineNumber;
405
public NUnitVisitor (CSharpAstResolver resolver)
407
this.resolver = resolver;
410
string GetFullName (TypeDeclaration typeDeclaration)
412
var parts = new List<string> ();
415
parts.Add (typeDeclaration.Name);
416
if (typeDeclaration.Parent is TypeDeclaration) {
417
typeDeclaration = (TypeDeclaration)typeDeclaration.Parent;
423
var ns = typeDeclaration.Parent as NamespaceDeclaration;
425
parts.Add (ns.FullName);
427
return string.Join (".", parts);
430
void AppendConstant (StringBuilder sb, object constantValue)
432
if (constantValue is string)
434
if (constantValue is char)
436
sb.Append (constantValue);
437
if (constantValue is string)
439
if (constantValue is char)
443
string BuildArguments (IAttribute attr)
445
StringBuilder sb = new StringBuilder ();
446
foreach (var arg in attr.PositionalArguments) {
449
var cr = arg as ConversionResolveResult;
451
AppendConstant (sb, cr.Input.ConstantValue);
454
AppendConstant (sb, arg.ConstantValue);
456
return sb.ToString ();
459
public override void VisitMethodDeclaration (MethodDeclaration methodDeclaration)
461
var result = resolver.Resolve (methodDeclaration) as MemberResolveResult;
464
var method = result.Member as IMethod;
466
UnitTest test = null;
468
foreach (var attr in method.Attributes) {
469
if (attr.AttributeType.ReflectionName == "NUnit.Framework.TestAttribute") {
471
test = new UnitTest (methodDeclaration.NameToken.StartLocation.Line);
472
test.UnitTestIdentifier = GetFullName ((TypeDeclaration)methodDeclaration.Parent) + "." + methodDeclaration.Name;
473
foundTests.Add (test);
475
} else if (attr.AttributeType.ReflectionName == "NUnit.Framework.TestCaseAttribute") {
476
test.TestCases.Add ("(" + BuildArguments (attr) + ")");
481
public override void VisitTypeDeclaration (TypeDeclaration typeDeclaration)
483
var result = resolver.Resolve (typeDeclaration);
485
foreach (var attr in result.Type.GetDefinition ().Attributes) {
487
if (attr.AttributeType.ReflectionName == "NUnit.Framework.TestFixtureAttribute") {
488
var unitTest = new UnitTest (typeDeclaration.NameToken.StartLocation.Line);
489
unitTest.IsFixture = true;
490
unitTest.UnitTestIdentifier = GetFullName (typeDeclaration);
491
foundTests.Add (unitTest);
494
base.VisitTypeDeclaration (typeDeclaration);
497
public override void VisitBlockStatement (BlockStatement blockStatement)