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