Tuesday, March 16, 2010

Using Reflection to Automatically Parse Command Line Arguments

It's been a while since I wrote a real command line application (long before C# got reflection) and haven't had to parse command line arguments in quite some time. Here is my new ParseArgs() function using reflection. The basic assumption is that arguments are being used to set static Properties of the entry point class.

public static void ParseArgs (string [] args)
{
    // Arguments are assumed to come in pairs, e.g.:
    //    program /arg1 arg_value1 /arg2 arg_value2
    Validate.True ("Number of arguments is even",
        0 == (args.Length & 1));

    MethodInfo method_info;
    string convert_method;
    object [] parameters = new object [1];
    Type [] string_type = { typeof (String) };

    // Get the type for where ever this is being called
    //   There must be a better way to do this
    Type this_type = new StackTrace ().GetFrame (0).GetMethod ().ReflectedType;

    try
    {
        // Loop through each argument pair (i += 2), assumed format is:
        //    program /arg1 arg_value1 /arg2 arg_value2
        // Other formats are not supported.  arg1, arg2, etc are assumed
        // to be static properties for this_type.
        for (int i = 0; i < args.Length; i += 2)
        {
            // Trim the leading slash on the property name
            Validate.True ("Arguments start with a slash(/)",
                args [i] [0].Equals ('/'));
            string prop_name = args [i].TrimStart ('/');

            // Get the property associated with this argument. We don't
            // have an instance, so only static properties are appropriate.
            PropertyInfo pi = this_type.GetProperty (prop_name, 
                BindingFlags.IgnoreCase | 
                BindingFlags.Static | 
                BindingFlags.SetProperty | 
                BindingFlags.Public);

            // If there is no match then just continue
            if (null == pi)
                continue;

            // Get the Setter mthod for this property
            method_info = pi.GetSetMethod ();

            // Conversion is based on the Convert class.

            // Here we try to assemble the name for the appropriate
            // method of Convert. Methods are of the form "To____(String)"
            // where "____" is the Type we are trying to convert the
            // string two.
            convert_method = "To" + pi.PropertyType.Name;

            // Get the Convert method
            MethodInfo m = typeof (Convert).GetMethod (
                convert_method, string_type);

            // We will pass the current arg_value to the Convert method 
            parameters [0] = args [i + 1];

            // Invoke the convert method.  The result will be passed to
            // the property's Setter method.
            parameters [0] = m.Invoke (typeof (Convert), parameters);

            // Invoke the setter method on this_type
            method_info.Invoke (this_type, parameters);
        }
    }
    catch (Exception ex)
    {
        new LoggedException (ex, "Failed to parse command line arguments").Log ();
    }
}

Wednesday, March 10, 2010

Automated Retries

I'm still working on building up an automated GUI testing framework. It turns out that many GUI interaction are not very reliable for a number of reasons (the time it takes a for a dialog to pop-up can vary from session to session, for example).

To solve this my framework will retry some operations until successful or some limit is reached. Of course I don't want to write the same retry logic over and over again, so I came up with this little library function that makes use of lambdas to keep it generic.

public class AutomatedRetry
{
    public uint RETRY_FACTOR = 50;

    public bool Try (Func<bool> action, uint limit)
    {
        for (uint tries = 0; tries < limit; tries++)
        {
            if (action ())
                return true;
            else
            {
                uint time = tries * RETRY_FACTOR;
                Log ("  retry in {0} ms", time);

                Thread.Sleep ((int)time);
            }
        }

        return false;
    }

    ...
}

The function Try() takes a delegate that returns true when successful and false otherwise. It will keep calling action until it returns true or the limit is reached.

Here is an example of it being used:

public AutomationElement FindElementByName (
    AutomationElement root, string name)
{
    AutomationElement elm = null;

    Func<bool> action = () =>
    {
        elm = FindElementByName_ (root, name);

        return null != elm;
    };

    AutomatedRetry trier = new AutomatedRetry ();
    trier.Try (action, RETRY_LIMIT);

    return elm;
}

I make a delegate (action) that calls the method that is going to get retried and tests the result to see if it was successful or not. In this case we are trying to find a UI Element by it's name. FindElementByName_() will return the element if successful or null if not. To use the AutomatedRetry we simple call Try() with the action delegate and the number of times to retry. Pretty cool, huh?