1 using System;
2 using System.IO;
3 using System.Threading;
4 using System.Diagnostics;
5 using System.Collections;
6 using System.Collections.Specialized;
7 using System.Runtime.Remoting;
8 using System.Runtime.Remoting.Services;
9 using System.Runtime.Remoting.Channels;
10 using System.Runtime.Remoting.Channels.Tcp;
11 using NUnit.Core;
12 
13 namespace NUnit.Util
14 {
15 	/// <summary>
16 	/// Enumeration of agent types used to request agents
17 	/// </summary>
18 	[Flags]
19 	public enum AgentType
20 	{
21 		Default = 0,
22 		DomainAgent = 1, // NYI
23 		ProcessAgent = 2
24 	}
25 
26 	/// <summary>
27 	/// Enumeration used to report AgentStatus
28 	/// </summary>
29 	public enum AgentStatus
30 	{
31 		Unknown,
32 		Starting,
33 		Ready,
34 		Busy,
35 		Stopping
36 	}
37 
38 	/// <summary>
39 	/// The TestAgency class provides RemoteTestAgents
40 	/// on request and tracks their status. Agents
41 	/// are wrapped in an instance of the TestAgent
42 	/// class. Multiple agent types are supported
43 	/// but only one, ProcessAgent is implemented
44 	/// at this time.
45 	/// </summary>
46 	public class TestAgency : ServerBase, IService
47 	{
48 		#region Private Fields
49 		private AgentDataBase agentData = new AgentDataBase();
50 
51 		private AgentType supportedAgentTypes = AgentType.ProcessAgent;
52 
53 		private AgentType defaultAgentType = AgentType.ProcessAgent;
54 		#endregion
55 
56 		#region Constructors
TestAgency()57 		public TestAgency() : this( "TestAgency", 9100 ) { }
58 
TestAgency( string uri, int port )59 		public TestAgency( string uri, int port ) : base( uri, port ) { }
60 		#endregion
61 
62 		#region Static Property - TestAgentExePath
63 		public static string TestAgentExePath
64 		{
65 			get
66 			{
67 				string agentPath = "nunit-agent.exe";
68 
69 				if ( !File.Exists(agentPath) )
70 				{
71 					DirectoryInfo dir = new DirectoryInfo( Environment.CurrentDirectory );
72 					if ( dir.Parent.Name == "bin" )
73 						dir = dir.Parent.Parent.Parent.Parent;
74 
75 					string path = PathUtils.Combine( dir.FullName, "NUnitTestServer", "nunit-agent-exe",
76 						"bin", NUnitFramework.BuildConfiguration, "nunit-agent.exe" );
77 					if( File.Exists( path ) )
78 						agentPath = path;
79 				}
80 
81 				return agentPath;
82 			}
83 		}
84 		#endregion
85 
86 		#region ServerBase Overrides
Stop()87 		public override void Stop()
88 		{
89 			foreach( AgentRecord r in agentData )
90 			{
91 				if ( !r.Process.HasExited )
92 				{
93 					if ( r.Agent != null )
94 						r.Agent.Stop();
95 
96 					//r.Process.Kill();
97 				}
98 			}
99 
100 			agentData.Clear();
101 
102 			base.Stop ();
103 		}
104 		#endregion
105 
106 		#region Public Methods - Called by Agents
Register( RemoteTestAgent agent, int pid )107 		public void Register( RemoteTestAgent agent, int pid )
108 		{
109 			AgentRecord r = agentData[pid];
110 			if ( r == null )
111 				throw new ArgumentException( "Specified process is not in the agency database", "pid" );
112 			r.Agent = agent;
113 		}
114 
ReportStatus( int pid, AgentStatus status )115 		public void ReportStatus( int pid, AgentStatus status )
116 		{
117 			AgentRecord r = agentData[pid];
118 
119 			if ( r == null )
120 				throw new ArgumentException( "Specified process is not in the agency database", "pid" );
121 
122 			r.Status = status;
123 		}
124 		#endregion
125 
126 		#region Public Methods - Called by Clients
GetAgent()127 		public TestAgent GetAgent()
128 		{
129 			return GetAgent( AgentType.Default, 5000 );
130 		}
131 
GetAgent( AgentType type )132 		public TestAgent GetAgent( AgentType type )
133 		{
134 			return GetAgent( type, 5000 );
135 		}
136 
GetAgent(AgentType type, int waitTime)137 		public TestAgent GetAgent(AgentType type, int waitTime)
138 		{
139 			if ( type == AgentType.Default )
140 				type = defaultAgentType;
141 
142 			if ( (type & supportedAgentTypes) == 0 )
143 				throw new ArgumentException(
144 					string.Format( "AgentType {0} is not supported by this agency", type ),
145 					"type" );
146 
147 			AgentRecord r = FindAvailableRemoteAgent(type);
148 			if ( r == null )
149 				r = CreateRemoteAgent(type, waitTime);
150 
151 			return new TestAgent( this, r.Process.Id, r.Agent );
152 		}
153 
ReleaseAgent( TestAgent agent )154 		public void ReleaseAgent( TestAgent agent )
155 		{
156 			AgentRecord r = agentData[agent.Id];
157 			if ( r == null )
158 				NTrace.Error( string.Format( "Unable to release agent {0} - not in database", agent.Id ) );
159 			else
160 			{
161 				r.Status = AgentStatus.Ready;
162 				NTrace.Debug( "Releasing agent " + agent.Id.ToString() );
163 			}
164 		}
165 
DestroyAgent( TestAgent agent )166 		public void DestroyAgent( TestAgent agent )
167 		{
168 			AgentRecord r = agentData[agent.Id];
169 			if ( r != null )
170 			{
171 				if( !r.Process.HasExited )
172 					r.Agent.Stop();
173 				agentData[r.Process.Id] = null;
174 			}
175 		}
176 		#endregion
177 
178 		#region Helper Methods
LaunchAgentProcess()179 		private int LaunchAgentProcess()
180 		{
181 			//ProcessStartInfo startInfo = new ProcessStartInfo( TestAgentExePath, ServerUtilities.MakeUrl( this.uri, this.port ) );
182 			//startInfo.CreateNoWindow = true;
183 			Process p = new Process();
184 			if ( Type.GetType( "Mono.Runtime", false ) != null )
185 			{
186 				p.StartInfo.FileName = @"C:\Program Files\mono-1.2.5\bin\mono.exe";
187 				p.StartInfo.Arguments = TestAgentExePath + " " + ServerUtilities.MakeUrl( this.uri, this.port );
188 			}
189 			else
190 			{
191 				p.StartInfo.FileName = TestAgentExePath;
192 				p.StartInfo.Arguments = ServerUtilities.MakeUrl( this.uri, this.port );
193 			}
194 
195 			//NTrace.Debug( "Launching {0}" p.StartInfo.FileName );
196 			p.Start();
197 			agentData.Add( new AgentRecord( p.Id, p, null, AgentStatus.Starting ) );
198 			return p.Id;
199 		}
200 
FindAvailableRemoteAgent(AgentType type)201 		private AgentRecord FindAvailableRemoteAgent(AgentType type)
202 		{
203 			foreach( AgentRecord r in agentData )
204 				if ( r.Status == AgentStatus.Ready )
205 				{
206 					NTrace.DebugFormat( "Reusing agent {0}", r.Id );
207 					r.Status = AgentStatus.Busy;
208 					return r;
209 				}
210 
211 			return null;
212 		}
213 
CreateRemoteAgent(AgentType type, int waitTime)214 		private AgentRecord CreateRemoteAgent(AgentType type, int waitTime)
215 		{
216 			int pid = LaunchAgentProcess();
217 
218 			NTrace.DebugFormat( "Waiting for agent {0} to register", pid );
219 			while( waitTime > 0 )
220 			{
221 				int pollTime = Math.Min( 200, waitTime );
222 				Thread.Sleep( pollTime );
223 				waitTime -= pollTime;
224 				if ( agentData[pid].Agent != null )
225 				{
226 					NTrace.DebugFormat( "Returning new agent record {0}", pid );
227 					return agentData[pid];
228 				}
229 			}
230 
231 			return null;
232 		}
233 		#endregion
234 
235 		#region IService Members
236 
UnloadService()237 		public void UnloadService()
238 		{
239 			this.Stop();
240 		}
241 
InitializeService()242 		public void InitializeService()
243 		{
244 			this.Start();
245 		}
246 
247 		#endregion
248 
249 		#region Nested Class - AgentRecord
250 		private class AgentRecord
251 		{
252 			public int Id;
253 			public Process Process;
254 			public RemoteTestAgent Agent;
255 			public AgentStatus Status;
256 
AgentRecord( int id, Process p, RemoteTestAgent a, AgentStatus s )257 			public AgentRecord( int id, Process p, RemoteTestAgent a, AgentStatus s )
258 			{
259 				this.Id = id;
260 				this.Process = p;
261 				this.Agent = a;
262 				this.Status = s;
263 			}
264 
265 		}
266 		#endregion
267 
268 		#region Nested Class - AgentDataBase
269 		/// <summary>
270 		///  A simple class that tracks data about this
271 		///  agencies active and available agents
272 		/// </summary>
273 		private class AgentDataBase : IEnumerable
274 		{
275 			private ListDictionary agentData = new ListDictionary();
276 
277 			public AgentRecord this[int id]
278 			{
279 				get { return (AgentRecord)agentData[id]; }
280 				set
281 				{
282 					if ( value == null )
283 						agentData.Remove( id );
284 					else
285 						agentData[id] = value;
286 				}
287 			}
288 
289 			public AgentRecord this[RemoteTestAgent agent]
290 			{
291 				get
292 				{
293 					foreach( System.Collections.DictionaryEntry entry in agentData )
294 					{
295 						AgentRecord r = (AgentRecord)entry.Value;
296 						if ( r.Agent == agent )
297 							return r;
298 					}
299 
300 					return null;
301 				}
302 			}
303 
Add( AgentRecord r )304 			public void Add( AgentRecord r )
305 			{
306 				agentData[r.Id] = r;
307 			}
308 
Clear()309 			public void Clear()
310 			{
311 				agentData.Clear();
312 			}
313 
314 			#region IEnumerable Members
GetEnumerator()315 			public IEnumerator GetEnumerator()
316 			{
317 				return new AgentDataEnumerator( agentData );
318 			}
319 			#endregion
320 
321 			#region Nested Class - AgentDataEnumerator
322 			public class AgentDataEnumerator : IEnumerator
323 			{
324 				IEnumerator innerEnum;
325 
AgentDataEnumerator( IDictionary list )326 				public AgentDataEnumerator( IDictionary list )
327 				{
328 					innerEnum = list.GetEnumerator();
329 				}
330 
331 				#region IEnumerator Members
Reset()332 				public void Reset()
333 				{
334 					innerEnum.Reset();
335 				}
336 
337 				public object Current
338 				{
339 					get { return ((DictionaryEntry)innerEnum.Current).Value; }
340 				}
341 
MoveNext()342 				public bool MoveNext()
343 				{
344 					return innerEnum.MoveNext();
345 				}
346 				#endregion
347 			}
348 			#endregion
349 		}
350 
351 		#endregion
352 	}
353 }
354