My Blahg

April 3, 2007

NUnitForms and Modal Dialogs

Filed under: c#, dotnet — treyhutcheson @ 12:35 pm

Two weeks ago I transitioned to another project at work. This project is an automated test harness for an api. The api that is being tested is written in c#. It wraps access to a web service, and provides dialogs for selecting inputs to the web service. This api is an integration layer that is exposed as a set of reverse com interop objects, and is consumed from a legacy Delphi application.

The test harness is used to automate integration tests. It will use the api to make calls against the web service. But to make sure that the requests being sent to the web service are actually coming from the user interface rather than being programmatically generated via the api on an object level, the test harness must drive the user interface itself programmatically.

I came to the project rather late, so the toolset had pretty much been chosen for me. To drive the UI, we’re using the alpha 5 release of NUnitForms 2. This is my first encounter with NUnitForms, and I dig it. I gave up on writing GUI’s years ago, except where absolutely necessary, so I never had any interest in NUnitForms. Now that I’ve used it, I must say that it offers a great amount of utility.

Being an alpha release, I’ve run into a few problems. For the most part, I’ve been able to get around them by changing my approach. However, I encountered an issue that really threw me for a loop.

All of the dialogs exposed through the api are modal. A few of the dialogs are only launched from other modal dialogs. So to get to dialog B, one must first launch Dialog A and click on a button on Dialog A. NUnitForms provides a mechanism for calling back into test cases after a modal dialog has been displayed: the ModalDialogTester class. This class has a public method named ExpectModal, which takes the name of the form to watch for, and a delegate that is used as a callback after the dialog has been displayed.

This mechanism has worked for the most part, except for those cases where a modal dialog launches another modal dialog. I would encounter an AmbiguousNameException, stating that more than one form was present with the same name. What makes the situation so weird is that if I put a breakpoint anywhere between the display of the first modal dialog and the second, I would not receive the exception. When I ran the application outside of the debugging environment, there would be no exception. So I just decided to live with it while debugging.

I was wrong. For a single test script, there was no AmbiguousNameException outside of the debugging environment. But if I processed more than one test script in batch, I would get the exception. I beat my head against this issue for a solid two days. I couldn’t find the source code for any revision of NUnitForms 2, so I just downloaded the source code for version 1.3.1. Looking into the code, the ModalFormTester class internally makes use of the FormFinder class.

Now the FormFinder class is interesting. The FindAll method accepts a string(the name of the form to find), and uses the Win32 API to enumerate all top-level windows(those windows underneath the result of the GetDesktopWindow api). Inside the windows enumeration callback, the FormFinder calls Control.FromHandle(hwnd) to get a reference to the enumerated handle as a WinForms control. This method is static, and I didn’t know it even existed. If it’s not a WinForms control, the result is null. So the result is cast as a Form, and if it isn’t null, the form’s name property is compared against the name argument passed in to the FindAll method. If the name matches, the form instance is added to a collection. The collection is returned from FindAll.

The Find method(singular) internally calls the FindAll method. If no forms are found, it throws a NoSuchControlException. If more than one form is found, it throws an AmbiguousNameException. So I was able to track down where the exception is being thrown, but I couldn’t figure out the condition causing more than one form with the same name to be found. I know there’s only one being instantiated and displayed.

After a few days of futility, I decided that enough was enough. Maybe this wouldn’t be an issue if we weren’t using an alpha build, but that’s out of my control. To solve my problem, I implemented two new classes: CustomFormFinder and ModalFormListener. CustomFormFinder effectively duplicates the logic of the original FormFinder class, except it doesn’t throw any exceptions. It is up to the caller to determine if zero, or more than 1 forms, is an exceptional circumstance. One improvement that I made is that the methods are all static, possible due to dotnet 2’s ability to define delegates anonymously. This way I can have my Windows Enumeration method implemented inline inside of the parent method; the benefit here is that the class is now completely stateless. Another improvement is that I added genericized overloads to both FindForms and FindSingleForm. The generic overloads don’t compare against form name; rather they find all forms of a given type. For example, the method signature of the genericized FindSingleForm looks like this:

public static T FindSingleForm<T>() where T : Form;

After I created and tested the CustomFormFinder class, I implemented the ModalFormListener class. This class has a series of overloaded static methods named RegisterModalCallback. These methods will invoke a callback after a modal form has been displayed. The overloads either work off of form name, or form type T. Like the CustomFormFinder, the methods are static, so all state has been eliminated.

Now the primary difference between my ModalFormListener and NUnitForms’s ModalFormTester is the way I look for forms. I couldn’t find the source code for the ModalFormTester::ExpectModal method, so I’m shooting in the dark here. I assume that it’s using system hooks to be capture any ACTIVATE messages. When a matching form is found, the callback is fired. In the case of my ModalFormListener, I simply fire off a timer into a callback method. Each timer internal I call CustomFormFinder to find the requested form, and if it’s found, I disable the timer and invoke the callback.

It works, and the best part is that I don’t get any unpredictable behavior. And one benefit of using the timer is that the timer event handler is invoked on the main thread, which means that the callback is itself invoked on the main thread.

March 9, 2007

Followup to “Odd MSI Behavior”

Filed under: Uncategorized — treyhutcheson @ 10:34 am

I haven’t quite figured out why the batch file was being executed only when verbose logging was enabled. I didn’t decorate the custom action’s sequence with any conditions. So it should have executed every single time.

Because I could never figure it out, I reexamined how I was executing the batch file in the first place. I spent almost an entire day googling various forms of CustomAction and ExeCommand, when I finally stumbled across Robert Pickering. It seems he experienced a issues somewhat related to my problem almost 3 years ago(June of 04). I specifically found his post regarding NGen, and while that post didn’t address my problem, bits from some of other posts were helpful. Thanks for blogging, Robert.

As I mentioned in my last post, I must execute the service.bat file included in my msi so that Tomcat can be installed as a service. I tried various forms of CustomAction, and finally found something that works.

The first action that I’ve defined simply resolves the path to the service.bat file at installation time. It looks like this:
<CustomAction Id=’SERVICE.SETPATH’ Property=’SERVICE.PATH’ Value=’[tomcat.bin]service.bat’ />

This action is sequenced to execute before UnpublishComponents, so that I know the path to the file for installation, and for uninstallation. This action creates a new property named SERVICE.PATH, and expands it’s value to be [tomcat.bin]service.bat. The [tomcat.bin] property is the name of tomcat’s bin folder. This directory is cleared in a separate .wxs file.

The next action actually executes the batch file:
<CustomAction Id=’SERVICE.INSTALL’ Directory=’tomcat.bin’ ExeCommand=’”[SERVICE.PATH]” install’ />
This action, sequenced after InstallFinalize, executes the file declared in the SERVICE.PATH property(service.bat) in the tomcat.bin folder.

That’s all there is to it. Of course, I wouldn’t have wasted damned near a week on this if the documentation were more clear. For those that aren’t familiar with WiX, the CustomAction element has a series of attributes that can be used in different combinations, sometimes mutually exclusively. The various combination of those attributes produce different types of custom actions by the msi compiler. The MSI documentation simply calls these “Type 34″ or “Type 50″ custom actions. Very clear there, Microsoft. So when one first starts off with WiX, does he need a Type 34 custom action? A type 50 custom action? It’s all rather vague, and as a result, one can spend countless hours experimenting.

Blog at WordPress.com.