using System; using System.IO; using System.Threading; using System.Diagnostics; using System.Collections; using System.Collections.Specialized; using System.Runtime.Remoting; using System.Runtime.Remoting.Services; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Tcp; using NUnit.Core; namespace NUnit.Util { /// /// Enumeration of agent types used to request agents /// [Flags] public enum AgentType { Default = 0, DomainAgent = 1, // NYI ProcessAgent = 2 } /// /// Enumeration used to report AgentStatus /// public enum AgentStatus { Unknown, Starting, Ready, Busy, Stopping } /// /// The TestAgency class provides RemoteTestAgents /// on request and tracks their status. Agents /// are wrapped in an instance of the TestAgent /// class. Multiple agent types are supported /// but only one, ProcessAgent is implemented /// at this time. /// public class TestAgency : ServerBase, IService { #region Private Fields private AgentDataBase agentData = new AgentDataBase(); private AgentType supportedAgentTypes = AgentType.ProcessAgent; private AgentType defaultAgentType = AgentType.ProcessAgent; #endregion #region Constructors public TestAgency() : this( "TestAgency", 9100 ) { } public TestAgency( string uri, int port ) : base( uri, port ) { } #endregion #region Static Property - TestAgentExePath public static string TestAgentExePath { get { string agentPath = "nunit-agent.exe"; if ( !File.Exists(agentPath) ) { DirectoryInfo dir = new DirectoryInfo( Environment.CurrentDirectory ); if ( dir.Parent.Name == "bin" ) dir = dir.Parent.Parent.Parent.Parent; string path = PathUtils.Combine( dir.FullName, "NUnitTestServer", "nunit-agent-exe", "bin", NUnitFramework.BuildConfiguration, "nunit-agent.exe" ); if( File.Exists( path ) ) agentPath = path; } return agentPath; } } #endregion #region ServerBase Overrides public override void Stop() { foreach( AgentRecord r in agentData ) { if ( !r.Process.HasExited ) { if ( r.Agent != null ) r.Agent.Stop(); //r.Process.Kill(); } } agentData.Clear(); base.Stop (); } #endregion #region Public Methods - Called by Agents public void Register( RemoteTestAgent agent, int pid ) { AgentRecord r = agentData[pid]; if ( r == null ) throw new ArgumentException( "Specified process is not in the agency database", "pid" ); r.Agent = agent; } public void ReportStatus( int pid, AgentStatus status ) { AgentRecord r = agentData[pid]; if ( r == null ) throw new ArgumentException( "Specified process is not in the agency database", "pid" ); r.Status = status; } #endregion #region Public Methods - Called by Clients public TestAgent GetAgent() { return GetAgent( AgentType.Default, 5000 ); } public TestAgent GetAgent( AgentType type ) { return GetAgent( type, 5000 ); } public TestAgent GetAgent(AgentType type, int waitTime) { if ( type == AgentType.Default ) type = defaultAgentType; if ( (type & supportedAgentTypes) == 0 ) throw new ArgumentException( string.Format( "AgentType {0} is not supported by this agency", type ), "type" ); AgentRecord r = FindAvailableRemoteAgent(type); if ( r == null ) r = CreateRemoteAgent(type, waitTime); return new TestAgent( this, r.Process.Id, r.Agent ); } public void ReleaseAgent( TestAgent agent ) { AgentRecord r = agentData[agent.Id]; if ( r == null ) NTrace.Error( string.Format( "Unable to release agent {0} - not in database", agent.Id ) ); else { r.Status = AgentStatus.Ready; NTrace.Debug( "Releasing agent " + agent.Id.ToString() ); } } public void DestroyAgent( TestAgent agent ) { AgentRecord r = agentData[agent.Id]; if ( r != null ) { if( !r.Process.HasExited ) r.Agent.Stop(); agentData[r.Process.Id] = null; } } #endregion #region Helper Methods private int LaunchAgentProcess() { //ProcessStartInfo startInfo = new ProcessStartInfo( TestAgentExePath, ServerUtilities.MakeUrl( this.uri, this.port ) ); //startInfo.CreateNoWindow = true; Process p = new Process(); if ( Type.GetType( "Mono.Runtime", false ) != null ) { p.StartInfo.FileName = @"C:\Program Files\mono-1.2.5\bin\mono.exe"; p.StartInfo.Arguments = TestAgentExePath + " " + ServerUtilities.MakeUrl( this.uri, this.port ); } else { p.StartInfo.FileName = TestAgentExePath; p.StartInfo.Arguments = ServerUtilities.MakeUrl( this.uri, this.port ); } //NTrace.Debug( "Launching {0}" p.StartInfo.FileName ); p.Start(); agentData.Add( new AgentRecord( p.Id, p, null, AgentStatus.Starting ) ); return p.Id; } private AgentRecord FindAvailableRemoteAgent(AgentType type) { foreach( AgentRecord r in agentData ) if ( r.Status == AgentStatus.Ready ) { NTrace.DebugFormat( "Reusing agent {0}", r.Id ); r.Status = AgentStatus.Busy; return r; } return null; } private AgentRecord CreateRemoteAgent(AgentType type, int waitTime) { int pid = LaunchAgentProcess(); NTrace.DebugFormat( "Waiting for agent {0} to register", pid ); while( waitTime > 0 ) { int pollTime = Math.Min( 200, waitTime ); Thread.Sleep( pollTime ); waitTime -= pollTime; if ( agentData[pid].Agent != null ) { NTrace.DebugFormat( "Returning new agent record {0}", pid ); return agentData[pid]; } } return null; } #endregion #region IService Members public void UnloadService() { this.Stop(); } public void InitializeService() { this.Start(); } #endregion #region Nested Class - AgentRecord private class AgentRecord { public int Id; public Process Process; public RemoteTestAgent Agent; public AgentStatus Status; public AgentRecord( int id, Process p, RemoteTestAgent a, AgentStatus s ) { this.Id = id; this.Process = p; this.Agent = a; this.Status = s; } } #endregion #region Nested Class - AgentDataBase /// /// A simple class that tracks data about this /// agencies active and available agents /// private class AgentDataBase : IEnumerable { private ListDictionary agentData = new ListDictionary(); public AgentRecord this[int id] { get { return (AgentRecord)agentData[id]; } set { if ( value == null ) agentData.Remove( id ); else agentData[id] = value; } } public AgentRecord this[RemoteTestAgent agent] { get { foreach( System.Collections.DictionaryEntry entry in agentData ) { AgentRecord r = (AgentRecord)entry.Value; if ( r.Agent == agent ) return r; } return null; } } public void Add( AgentRecord r ) { agentData[r.Id] = r; } public void Clear() { agentData.Clear(); } #region IEnumerable Members public IEnumerator GetEnumerator() { return new AgentDataEnumerator( agentData ); } #endregion #region Nested Class - AgentDataEnumerator public class AgentDataEnumerator : IEnumerator { IEnumerator innerEnum; public AgentDataEnumerator( IDictionary list ) { innerEnum = list.GetEnumerator(); } #region IEnumerator Members public void Reset() { innerEnum.Reset(); } public object Current { get { return ((DictionaryEntry)innerEnum.Current).Value; } } public bool MoveNext() { return innerEnum.MoveNext(); } #endregion } #endregion } #endregion } }