Friday, January 15, 2010

Installing NUnit with Visual Studio 2005 on Windows Vista

This article isn't about how to use or configure NUnit, it is simply about getting it installed. The short version is listed at the end of this article.

Running Visual Studio 2005 on a Windows Vista platform, the documentation on the NUnit project web site was a bit anemic and left me perplexed. I posted my notes and experience here to help the next programmer who falls into the same trap of using inadequate documentation and having no previous experience with the product.

Getting started with the NUnit testing framework was rather like touching an electrified fence: the shock certainly woke me up. Now, don't think I don't like the package and the effort that went into it. I certainly want to thank the authors for putting together a very useful software framework that improves the quality of my code. On the other hand, it would be nicer if it were easier to get working or at least had some accurate installation instructions somewhere. To the best of my ability, here are those instructions.

The download page was pretty clear about what package you should use. Since I wanted to install on Windows and wasn't going to be developing NUnit itself, I picked the Windows binary Installer package. At the time this article was written, NUnit 2.5.3 was the current production release, so I downloaded the "win" installation archive, NUnit-2.5.3.9345.msi, and ran it on my development machine. I picked the Typical installation and got what I thought I needed. Seemed simple enough.

Following the text on the Installation page, I got to the Configuration section (about half a page) before the fog started setting in. The document mentions the configuration files are different depending on the NUnit executable you run. Personally I think this belongs in a different section of the document, but I could go back and look at it again later, so I just overlooked it for now.

The Installation Verification really threw me for a loop. It states:
Verify that the installation has worked successfully by running the NUnit gui and loading and running NUnitTests.nunit. All tests should pass.


Warning! Misleading Documentation Alert! OK, it's elementary that I can run the GUI NUnit by going to Start->NUnit 2.5.3->NUnit. Now I just needed to find NUnitTests.nunit. No dice. After searching fruitlessly through every folder the installer created, I deduced that somebody simply forgot to include the file in the installer archive. Oh well, mistakes happen. I went back to the web site and downloaded the previous version. Uninstalled 2.5.3 and installed 2.5.2. *#&*@^! Same problem. The file didn't exist in either of the latest packages, so I thought. Grrr!

After a brief email exchange with the project administrator, Charlie Poole, it turns out that performing the Typical install, the file is not available. Only if you install the Unit Tests for NUnit under .NET 1.1 does the noted file get installed. This is confusing and a revision of the documentation or installer is under consideration. I have to say this was very responsive and indicative of the quality of the package as I saw so far.

Well, I just skipped the verification and tried one of the samples. Curiously, there were no *.nunit files to be found anywhere. Maybe they were just for the GUI? I hadn't found it in the documentation so I moved on to compile a sample.

I use C# most of the time, so I used one of those samples. The samples\csharp directory contained several different tests organized under what appears to be a Visual Studio Solution so I opened CSharp.sln. OK, VS had to convert it to my version, so I let it and it didn't complain. Built the solution and got an error in MoneyTest.cs:

The type or namespace name 'Framework' does not exist in the namespace 'NUnit' (are you missing an assembly reference?)




I checked the references; no, it was there. But there was a warning label on the icon. Looking at its properties, the reference had no path and no version!

Now was Visual Studio giving me problems? I clicked on the project icon, added the reference again - at least I was able to pick it off the .NET reference list. It was named nunit.framework, not NUnit.Framework, so that didn't look right. But, no change, the reference still showed the warning label. @$#@#&%#@!

I removed the existing reference and added the reference again. Now it sorta looked right that the warning label was gone. Build, and the projects were compiled. I launched the NUnit GUI application. Selected Open Project... and browsed to the csharp/money directory. Nothing. Really? No, nothing. Drilled down into bin/Debug and find a couple .dll files, but still no *.nunit files. Tried loading the cs-money.dll assembly directly and I saw the tree of tests. That was promising. So I clicked the Run button and a green progress bar filled the status area.



Success!? Yes, no, maybe? Yes, I'll call it success.

My suspicion was the .dll assembly contained all the information an NUnit project file would have, but I didn't locate the documentation to prove it. Trial and error is a heck of a poor way to find out. Moral: bad documentation doesn't make for good testing. Your mileage may vary :-/.

Here's the short version of all this:
  1. Download the NUnit installer package.

  2. Run the installer.

  3. Skip the Installation Verification step because the files weren't installed.

  4. To create and use NUnit tests with a new project, add a reference to the nunit.framework.dll library to the project.

  5. To use NUnit with the sample projects, remove the existing nunit.framework.dll library references first. Add a reference to the newly installed library to the projects.

  6. Don't bother looking for *.nunit files. Or do a full install.



It's difficult to be grateful when you're annoyed. But at least I was running and demonstrated the framework will actually do what it's told, once I figured that out.

The End

Friday, January 8, 2010

Revised WorkerThread class and Bug in EventWaitHandle.WaitOne() in .Net CF 2.0

The WaitOne(int millisecondsTimeout, bool exitContext) method of the ManualResetEvent (based on EventWaitHandle) class allows waiting for a synchronization flag to be set, but timing out if it does not. On the .Net Compact Framework (.NetCF) 2.0, calling this method with exitContext set to true throws an ArgumentException exception. The fix is to always set exitContext to false.

How I got into this was researching .Net worker threads. There was a very good article written by Juval Löwy, titled Working with .NET Threads, and provided a useful wrapper class, named WorkerThread, to make managing threads easier in your application. Thank you, Juval!

I use .NetCF frequently and, as usual, found some incompatibilities I had to correct before the WorkerThread class worked on that platform. The revised and better-documented WorkerThread for .NetCF 2.0 is listed below. Even after rewriting for the methods missing in the compact framework, I ran across a bug in .NetCF 2.0 that prevents the class from working properly.

The wrapper class relies on several synchronization constructs in order to provide safe access to its functionality across threads. One such case is where an extra integrity check attempts to determine that the thread is actually running, in the code that implements the IsAlive property. As originally coded, it was:


public bool IsAlive
{
get
{
Debug.Assert(m_ThreadObj != null);
bool isAlive = m_ThreadObj.IsAlive;
bool handleSignaled = m_ThreadHandle.WaitOne(0,true);
Debug.Assert(handleSignaled == ! isAlive);
return isAlive;
}
}


The problem appears in the call to WaitOne(0,true). While it compiles fine, at runtime it throws an ArgumentException error instead of ignoring or saving & restoring the synchronization context (a completely different concept than the thread synchronization I'm writing about here). Simple to fix, but maddening to diagnose.

The other minor issues I fixed were:

  • No IsAlive property on the .NetCF Thread class. Reference was commented out. I added a new instance variable, m_IsAlive, to emulate the missing property.

  • No Join(TimeSpan timeout) method on the .NetCF Thread class. Translated that method to use Join(int millisecondsTimeout) instead.

  • Fixed IsAlive property code to avoid the ArgumentException error, as mentioned above.

  • Added a Yield() method to relinquish the processor to other threads as an alternative to using the more cryptic Thread.Sleep(1).

  • Added an overridable Work() method to allow this class to be inherited instead of copied and modified. Replace Work() in your derived class to implement your processing.

  • Modified the Run() method to call Work() and automatically manage the IsAlive property.



As a way to pass on the bits of knowledge I've gained from this exercise, here is the final source for the .NetCF 2.0 WorkerThread class:


////////////////////////////////////////////////////////////////////////////////
// File.......: WorkerThread.cs
// Author.....: Edward F Eaglehouse
// Date.......: 11/21/2009
// Notes......:
// Adapted for .NetCF 2.0 from the WorkerThread class by Juval Lowy in his
// article, Working with .NET Threads. The full article text can be found at
// http://www.devx.com/codemag/Article/17442/0/page/1.
////////////////////////////////////////////////////////////////////////////////

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

namespace gltech.trackaway
{
///
/// Class that implements a separate worker thread.
///

///
/// Based closely on the WorkerThread wrapper class described in the
/// article Working with .NET Threads,
/// by Juval Löwy. Modified so it works with the .NET Compact Framework.
///

public class WorkerThread : IDisposable
{

#region Protected Members

///
/// Synchronization construct for signalling completion of the thread.
///

protected ManualResetEvent m_ThreadHandle;

///
/// Contains a reference to the uninheritable underlying Thread object.
///

protected Thread m_ThreadObj;

///
/// Flag to indicate that thread processing should be stopped.
///

protected bool m_EndLoop;

///
/// Synchronization construct to protect the EndLoop flag.
///

protected Mutex m_EndLoopMutex;

///
/// Flag to indicate the thread is actively running.
///

protected bool m_IsAlive;

#endregion

#region Constructors

///
/// Initialize a new instance of the WorkerThread class.
///

public WorkerThread()
{
m_EndLoop = false;
m_ThreadObj = null;
m_EndLoopMutex = new Mutex();
m_ThreadHandle = new ManualResetEvent(false);

ThreadStart threadStart = new ThreadStart(Run);
m_ThreadObj = new Thread(threadStart);
m_ThreadObj.Name = "Worker Thread";
m_IsAlive = false;
}

///
/// Initialize a new instance of the WorkerThread class.
///

/// True to launch the thread immediately; otherwise, false.
public WorkerThread(bool autoStart)
: this()
{
if (autoStart)
{
Start();
}
}

#endregion

#region Properties

///
/// Get the underlying Thread object.
///

public Thread Thread
{
get
{
return m_ThreadObj;
}
}

///
/// Get the thread completion synchronization object.
///

public WaitHandle Handle
{
get
{
return m_ThreadHandle;
}
}

///
/// Get the indicator that the thread is active.
///

public bool IsAlive
{
get
{
Debug.Assert(m_ThreadObj != null);
//bool isAlive = m_Thread.IsAlive;
bool isAlive = this.m_IsAlive;
//bool handleSignaled = m_ThreadHandle.WaitOne(0, true);
bool handleSignaled = m_ThreadHandle.WaitOne(0, false);
Debug.Assert(handleSignaled == !isAlive);
return isAlive;
}
}

///
/// Get or set the indicator that processing should stop.
///

protected bool EndLoop
{
set
{
m_EndLoopMutex.WaitOne();
m_EndLoop = value;
m_EndLoopMutex.ReleaseMutex();
}
get
{
bool result = false;
m_EndLoopMutex.WaitOne();
result = m_EndLoop;
m_EndLoopMutex.ReleaseMutex();
return result;
}
}

///
/// Get or set the debuggable thread name.
///

public string Name
{
get
{
return m_ThreadObj.Name;
}
set
{
m_ThreadObj.Name = value;
}
}

#endregion

#region Public Methods

///
/// Launch the thread.
///

public void Start()
{
Debug.Assert(m_ThreadObj != null);
//Debug.Assert(m_ThreadObj.IsAlive == false);
m_ThreadObj.Start();
} // Start()

///
/// Overridable method that implements the processing to be done.
///

public virtual void Work()
{
int i = 0;
while (EndLoop == false)
{
Debug.WriteLine("Thread is alive, Counter is " + i);
i++;
}
} // Work()

///
/// Destroy this WorkerThread instance.
///

public void Dispose()
{
Kill();
} // Dispose()

///
/// Notify the processing loop to stop.
///

public void Kill()
{
//Kill is called on client thread - must use cached object
Debug.Assert(m_ThreadObj != null);
if (IsAlive == false)
{
return;
}
EndLoop = true;
//Wait for thread to die
Join();
m_EndLoopMutex.Close();
m_ThreadHandle.Close();
} // Kill()

///
/// Blocks the calling thread until this thread terminates.
///

public void Join()
{
Debug.Assert(m_ThreadObj != null);
if (IsAlive == false)
{
return;
}
Debug.Assert(Thread.CurrentThread.GetHashCode() !=
m_ThreadObj.GetHashCode());
m_ThreadObj.Join();
} // Join()

///
/// Blocks the calling thread until this thread terminates or the
/// specified time elapses.
///

/// Number of milliseconds to wait.
///
public bool Join(int millisecondsTimeout)
{
TimeSpan timeout;
timeout = TimeSpan.FromMilliseconds(millisecondsTimeout);
return Join(timeout);
} // Join(int millisecondsTimeout)

///
/// Blocks the calling thread until this thread terminates or the
/// specified time elapses.
///

/// TimeSpan set to the amount of time to wait.
///
public bool Join(TimeSpan timeout)
{
int timeout_ms = (int)timeout.TotalMilliseconds;

Debug.Assert(m_ThreadObj != null);
if (IsAlive == false)
{
return true;
}
Debug.Assert(Thread.CurrentThread.GetHashCode() !=
m_ThreadObj.GetHashCode());
return m_ThreadObj.Join(timeout_ms);
} // Join(TimeSpan timeout)

///
/// Suspend this thread to allow other waiting threads to execute.
///

public void Yield()
{
// Yield processor to other threads, even ones at a lower priority.
Thread.Sleep(1);
} // Yield()

#endregion

#region Protected Methods

///
/// Do the processing defined by this thread.
///

protected void Run()
{
try
{
m_IsAlive = true;
Work();
}
finally
{
m_IsAlive = false;
m_ThreadHandle.Set();
}
} // Run()

#endregion

}
}