My Blahg

August 25, 2008

NAnt – waiting for some condition

Filed under: NAnt, c#, dotnet — treyhutcheson @ 10:27 am

Question for all of the NAnt guys out there: have you ever needed to wait on some condition or interval in your build scripts? I have. Fortunately however, it doesn’t happen very often in my build scripts. On the other hand, for the two or so cases where I needed to wait, I found that there is no matching behavior in NAnt or NAntContrib.

A couple of years ago I had to come up with some build and test scripts for some automated UI testing. We were obviously using NAnt, and used NUnitForms. Some of the test conditions were complicated, requiring sequenced events, while some others required some mock threading magic. Most of the really complicated stuff took place in the unit tests themselves. However, we did run into a couple of places where we needed to control things from the test scripts and not the actual c# test cases. Here we ran into sequencing and timing issues, and this is where I discovered that I needed to be able to “pause” the nant script for some interval, or to wait for some condition to become true.

The result is a custom NAnt task simply named WaitTask. You can think of it as an NAnt version of a ManualResetEvent. The basic premise is this: pause execution until some condition becomes true, or the condition times out. Optionally fail.

The simplest way to demonstrate this is through xml:

<wait timeout="1000" interval="50"
	failontimeout="true" for="${some.property=='true'}" />

Now lets look at the task’s properties.

timeout
This property defines the time, in milliseconds, in which the operation will timeout. If the condition does not evaluate to true during this time period, the task will end. In the example, the task has a timeout interval of one second.

interval
Defines the interval, in milliseconds, in which the condition will be evaluated. In the example, the condition will be evaluated every 50 milliseconds.

failontimeout
Indicates that the task will fail if the timeout occurs.

for
The boolean condition to evaluate at every interval. In the example, the task determines if the property named “some.property” has a string value of “true”.

And here’s the full code listing for the custom task:

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

using NAnt.Core;
using NAnt.Core.Attributes;
using NAnt.Core.Types;

namespace CustomNAntTasks
{
	/// <summary>
	/// This tasks waits until a condition is true
	/// </summary>
	[TaskName( "wait" )]
	public class WaitTask : Task
	{
		/// <summary>
		/// The timeout, in milliseconds
		/// </summary>
		private int _timeout;

		/// <summary>
		/// Indicates the task should fail on a timeout
		/// </summary>
		private bool _failOnTimeout;

		/// <summary>
		/// The expression to wait for
		/// </summary>
		private string _forExpression;

		/// <summary>
		/// The interval, in milliseconds, to use for the timer
		/// </summary>
		private int _timerInterval = 50;

		/// <summary>
		/// Indicates we are in a timer callback
		/// </summary>
		private bool _inTimer;

		/// <summary>
		/// The timer instance
		/// </summary>
		private ManualResetEvent _waitEvent;

		/// <summary>
		/// The result of the wait task
		/// </summary>
		private bool _result;

		public WaitTask()
		{
			_waitEvent = new ManualResetEvent( false );
		}	//END constructor 

		/// <summary>
		/// Gets/Sets the timeout in milliseconds
		/// </summary>
		[TaskAttribute( "timeout" )]
		public int Timeout
		{
			get
			{
				return _timeout;
			}
			set
			{
				_timeout = value;
			}
		}	//END Timeout property

		/// <summary>
		/// Indicates the task should fail on timeout
		/// </summary>
		[TaskAttribute( "failontimeout" )]
		public bool FailOnTimeout
		{
			get
			{
				return _failOnTimeout;
			}
			set
			{
				_failOnTimeout = value;
			}
		}	//END FailOnTimeout property

		/// <summary>
		/// Gets/Sets the For expression
		/// </summary>
		[TaskAttribute( "for" )]
		public string For
		{
			get
			{
				return _forExpression;
			}
			set
			{
				_forExpression = value;
			}
		}	//END For property

		/// <summary>
		/// Gets/Sets the interval, in milliseconds, to evaluate the condition
		/// </summary>
		[TaskAttribute( "interval" )]
		public int Interval
		{
			get
			{
				return _timerInterval;
			}
			set
			{
				_timerInterval = value;
			}
		}	//END TimerInterval property

		/// <summary>
		/// Executes the task
		/// </summary>
		protected override void ExecuteTask()
		{
			bool timedout = false;

			//setup the timer
			using( Timer t = new Timer( new TimerCallback( OnTimer ) , null , 0 , _timerInterval ) )
			{
				Log( Level.Info , "Waiting {0} milliseconds for condition to become true." , _timeout );

				_waitEvent.Reset();
				timedout = ! _waitEvent.WaitOne( _timeout , false );

			}	//dispose of the timer 

			if( timedout )
			{
				Level logLevel = _failOnTimeout ? Level.Error : Level.Info;
				Log( logLevel , "The wait operation timed out." );
			}
		}	//END ExecuteTask method 

		/// <summary>
		/// Indicates we are in a timer callback
		/// </summary>
		protected bool InTimer
		{
			get
			{
				lock( this )
					return _inTimer;
			}
			set
			{
				lock( this )
					_inTimer = value;
			}
		}	//END InTimer property

		/// <summary>
		/// The result of the wait task
		/// </summary>
		protected bool Result
		{
			get
			{
				lock( this )
					return _result;
			}
			set
			{
				lock( this )
					_result = value;
			}
		}	//END Result property

		/// <summary>
		/// Method called each timer interval
		/// </summary>
		/// <param name="state"></param>
		private void OnTimer( object state )
		{
			if( InTimer )
				return;

			try
			{
				InTimer = true;
				bool result = Evaluate();
				Log( Level.Info , "Evaluating condition: [{0}]" , _forExpression );

				if( result )
				{
					Log( Level.Info , "Evaluation result is True" );
					Result = true;
					_waitEvent.Set();
				}
				else
				{
					Log( Level.Info , "Evaluation unsuccessful. Trying again in {0} milliseconds" , _timerInterval );
				}
			}
			finally
			{
				InTimer = false;
			}
		}	//END OnTimer method 

		/// <summary>
		/// Determines if the wait condition is true
		/// </summary>
		private bool Evaluate()
		{
			ExpressionEvaluator eval = new ExpressionEvaluator( this.Project , this.Properties , null , null );
			return ( bool ) eval.Evaluate( _forExpression );
		}	//END Evaluate method 

	}	//END WaitTask class

}	//END namespace

ExecuteTask Method
When the task executes, it uses a System.Threading.Timer to call back into the current thread at the assigned interval. It then resets the private ManualResetEvent, then calls the WaitOne method on the event to suspend the current thread.

Timer Callback
Every time the timer callback is executed, the task calls the Evaluate method to evaluate the condition. If the condition is true, the event is set, and the task ends. If the condition is false, the timer callback leaves, waiting for the next callback, or timeout, whichever occurs first.

Condition Evaluation
This is the coolest part of the task because A) its extremely easy and B) it uses built-in NAnt objects. NAnt includes a class named NAnt.Core.ExpressionEvaluator. A new object of this class is instantiated, and the expression(reflected by the task’s for attribute) is evaluated. No fuss. The result of the evaluation is returned as the method result.

Conclusion
I don’t how helpful this task really is. Like I said at the beginning, I’ve only used it twice. However, this task did let me move some of my test case sequencing logic from outside of the test assembly and into the test script itself. Maybe some one can find another use for this task.

2 Comments »

  1. Do you have an example of a NAnt task that can run asynchronously with other NAnt tasks?

    Comment by mike — April 6, 2009 @ 12:41 pm

    • I’m sorry mike, but I have not had an occasion to do such a thing. Interesting problem though.

      Comment by treyhutcheson — April 7, 2009 @ 2:30 pm


RSS feed for comments on this post. TrackBack URI

Leave a comment

Blog at WordPress.com.