1
1
// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt
2
2
3
+ using System ;
3
4
using System . Collections . Generic ;
5
+ using System . IO ;
6
+ using System . Text ;
4
7
using System . Threading ;
5
8
using System . Xml ;
6
- using NUnit . Engine . Internal ;
7
9
8
10
namespace NUnit . Engine . Runners
9
11
{
@@ -25,18 +27,60 @@ namespace NUnit.Engine.Runners
25
27
/// Once the test has been cancelled, it provide notifications to the runner
26
28
/// so the information may be displayed.
27
29
/// </summary>
28
- internal class WorkItemTracker : ITestEventListener
30
+ internal sealed class WorkItemTracker : ITestEventListener
29
31
{
30
- private List < XmlNode > _itemsInProcess = new List < XmlNode > ( ) ;
31
- private ManualResetEvent _allItemsComplete = new ManualResetEvent ( false ) ;
32
- private object _trackerLock = new object ( ) ;
33
-
32
+ /// <summary>
33
+ /// Holds data about recorded test that started.
34
+ /// </summary>
35
+ private sealed class InProgressItem : IComparable < InProgressItem >
36
+ {
37
+ private readonly int _order ;
38
+
39
+ public InProgressItem ( int order , string name , XmlReader reader )
40
+ {
41
+ _order = order ;
42
+ Name = name ;
43
+
44
+ var attributeCount = reader . AttributeCount ;
45
+ Properties = new Dictionary < string , string > ( attributeCount ) ;
46
+ for ( var i = 0 ; i < attributeCount ; i ++ )
47
+ {
48
+ reader . MoveToNextAttribute ( ) ;
49
+ Properties . Add ( reader . Name , reader . Value ) ;
50
+ }
51
+ }
52
+
53
+ public string Name { get ; }
54
+ public Dictionary < string , string > Properties { get ; }
55
+
56
+ public int CompareTo ( InProgressItem other )
57
+ {
58
+ // for signaling purposes, return in reverse order
59
+ return _order . CompareTo ( other . _order ) * - 1 ;
60
+ }
61
+ }
62
+
63
+ // items are keyed by id
64
+ private readonly Dictionary < string , InProgressItem > _itemsInProcess = new Dictionary < string , InProgressItem > ( ) ;
65
+ private readonly ManualResetEvent _allItemsComplete = new ManualResetEvent ( false ) ;
66
+ private readonly object _trackerLock = new object ( ) ;
67
+
68
+ // incrementing ordering id for work items so we can traverse in correct order
69
+ private int _itemOrderNumberCounter = 1 ;
70
+
71
+ // when sending thousands of cancelled notifications, it makes sense to reuse string builder, used inside a lock
72
+ private readonly StringBuilder _notificationBuilder = new StringBuilder ( ) ;
73
+
74
+ // we want to write just the main element without XML declarations
75
+ private static readonly XmlWriterSettings XmlWriterSettings = new XmlWriterSettings { ConformanceLevel = ConformanceLevel . Fragment } ;
76
+
34
77
public void Clear ( )
35
78
{
36
79
lock ( _trackerLock )
37
80
{
38
81
_itemsInProcess . Clear ( ) ;
39
82
_allItemsComplete . Reset ( ) ;
83
+ _itemOrderNumberCounter = 1 ;
40
84
}
41
85
}
42
86
@@ -49,66 +93,85 @@ public void SendPendingTestCompletionEvents(ITestEventListener listener)
49
93
{
50
94
lock ( _trackerLock )
51
95
{
52
- int count = _itemsInProcess . Count ;
53
-
54
96
// Signal completion of all pending suites, in reverse order
55
- while ( count > 0 )
56
- listener . OnTestEvent ( CreateNotification ( _itemsInProcess [ -- count ] ) ) ;
57
- }
58
- }
97
+ var toNotify = new List < InProgressItem > ( _itemsInProcess . Values ) ;
98
+ toNotify . Sort ( ) ;
59
99
60
- private static string CreateNotification ( XmlNode startElement )
61
- {
62
- bool isSuite = startElement . Name == "start-suite" ;
63
-
64
- XmlNode notification = XmlHelper . CreateTopLevelElement ( isSuite ? "test-suite" : "test-case" ) ;
65
- if ( isSuite )
66
- notification . AddAttribute ( "type" , startElement . GetAttribute ( "type" ) ) ;
67
- notification . AddAttribute ( "id" , startElement . GetAttribute ( "id" ) ) ;
68
- notification . AddAttribute ( "name" , startElement . GetAttribute ( "name" ) ) ;
69
- notification . AddAttribute ( "fullname" , startElement . GetAttribute ( "fullname" ) ) ;
70
- notification . AddAttribute ( "result" , "Failed" ) ;
71
- notification . AddAttribute ( "label" , "Cancelled" ) ;
72
- XmlNode failure = notification . AddElement ( "failure" ) ;
73
- XmlNode message = failure . AddElementWithCDataSection ( "message" , "Test run cancelled by user" ) ;
74
- return notification . OuterXml ;
100
+ foreach ( var item in toNotify )
101
+ listener . OnTestEvent ( CreateNotification ( item ) ) ;
102
+ }
75
103
}
76
104
77
- void ITestEventListener . OnTestEvent ( string report )
105
+ private string CreateNotification ( InProgressItem item )
78
106
{
79
- XmlNode xmlNode = XmlHelper . CreateXmlNode ( report ) ;
107
+ _notificationBuilder . Clear ( ) ;
80
108
81
- lock ( _trackerLock )
109
+ using ( var stringWriter = new StringWriter ( _notificationBuilder ) )
82
110
{
83
- switch ( xmlNode . Name )
111
+ using ( var writer = XmlWriter . Create ( stringWriter , XmlWriterSettings ) )
84
112
{
85
- case "start-test" :
86
- case "start-suite" :
87
- _itemsInProcess . Add ( xmlNode ) ;
88
- break ;
89
-
90
- case "test-case" :
91
- case "test-suite" :
92
- string id = xmlNode . GetAttribute ( "id" ) ;
93
- RemoveItem ( id ) ;
94
-
95
- if ( _itemsInProcess . Count == 0 )
96
- _allItemsComplete . Set ( ) ;
97
- break ;
113
+ bool isSuite = item . Name == "start-suite" ;
114
+ writer . WriteStartElement ( isSuite ? "test-suite" : "test-case" ) ;
115
+
116
+ if ( isSuite )
117
+ writer . WriteAttributeString ( "type" , item . Properties [ "type" ] ) ;
118
+
119
+ writer . WriteAttributeString ( "id" , item . Properties [ "id" ] ) ;
120
+ writer . WriteAttributeString ( "name" , item . Properties [ "name" ] ) ;
121
+ writer . WriteAttributeString ( "fullname" , item . Properties [ "fullname" ] ) ;
122
+ writer . WriteAttributeString ( "result" , "Failed" ) ;
123
+ writer . WriteAttributeString ( "label" , "Cancelled" ) ;
124
+
125
+ writer . WriteStartElement ( "failure" ) ;
126
+ writer . WriteStartElement ( "message" ) ;
127
+ writer . WriteCData ( "Test run cancelled by user" ) ;
128
+ writer . WriteEndElement ( ) ;
129
+ writer . WriteEndElement ( ) ;
130
+
131
+ writer . WriteEndElement ( ) ;
98
132
}
133
+
134
+ return stringWriter . ToString ( ) ;
99
135
}
100
136
}
101
137
102
- private void RemoveItem ( string id )
138
+ void ITestEventListener . OnTestEvent ( string report )
103
139
{
104
- foreach ( XmlNode item in _itemsInProcess )
140
+ using ( var stringReader = new StringReader ( report ) )
141
+ using ( var reader = XmlReader . Create ( stringReader ) )
105
142
{
106
- if ( item . GetAttribute ( "id" ) == id )
143
+ // go to starting point
144
+ reader . MoveToContent ( ) ;
145
+
146
+ if ( reader . NodeType != XmlNodeType . Element )
147
+ throw new InvalidOperationException ( "Expected to find root element" ) ;
148
+
149
+ lock ( _trackerLock )
107
150
{
108
- _itemsInProcess . Remove ( item ) ;
109
- return ;
151
+ var name = reader . Name ;
152
+ switch ( name )
153
+ {
154
+ case "start-test" :
155
+ case "start-suite" :
156
+ var item = new InProgressItem ( _itemOrderNumberCounter ++ , name , reader ) ;
157
+ _itemsInProcess . Add ( item . Properties [ "id" ] , item ) ;
158
+ break ;
159
+
160
+ case "test-case" :
161
+ case "test-suite" :
162
+ RemoveItem ( reader . GetAttribute ( "id" ) ) ;
163
+
164
+ if ( _itemsInProcess . Count == 0 )
165
+ _allItemsComplete . Set ( ) ;
166
+ break ;
167
+ }
110
168
}
111
169
}
112
170
}
171
+
172
+ private void RemoveItem ( string id )
173
+ {
174
+ _itemsInProcess . Remove ( id ) ;
175
+ }
113
176
}
114
- }
177
+ }
0 commit comments