Archive for September, 2011

Strangeness with NUnit runner instantiating attributes before running tests are run

So Scott Koon and I going through converting a project from using MBUnit / Gallio to NUnit instead, mainly for the speed benefits, but also for complying with the direction of our development moving forward.  One gotcha we just ran into is in the way NUnit handles custom attributes.

Say you have an attribute like this:

    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
    public class DogMeowsLikeRatAttribute : Attribute
    {
        public DogMeowsLikeRatAttribute()
        {
            if (!StateMaintainer.HasBeenTouched)
                throw new ImmaCryException("Oh snap! You broke it!");
        }
    }

and a class that uses the attribute:

    [DogMeowsLikeRat]
    public class WhatDoWeAttributeThisTooClass
    {
        public void FoShizzel()
        {
            Console.WriteLine("Nizzle izzle da bomb dapibizzle turpizzle go to hizzle shiz");
        }
    }

and a test for your class

    [TestFixture]
    public class TestThatClassyClass
    {
        [Test]
        public void WhoaNow()
        {
            StateMaintainer.HasBeenTouched = true;

            var subject = new WhatDoWeAttributeThisTooClass();
            subject.Should().NotBeNull("Cause I said so!");
        }
    }

and the state maintainer just to prove the point:

    public static class StateMaintainer
    {
        public static bool HasBeenTouched;
    }

When you run the test, you will receive an exception. Now the important thing is to look at the stack trace here:

<pre>------ Test started: Assembly: DoesNunitInstantiateAttributes.dll ------

Here's what you're a gonna do: Oh snap! You broke it!
Test 'M:DoesNunitInstantiateAttributes.TestThatClassyClass.WhoaNow' failed: Oh snap! You broke it!
	DoesNunitInstantiateAttributes.ImmaCryException: Oh snap! You broke it!
	Class1.cs(18,0): at DoesNunitInstantiateAttributes.DogMeowsLikeRatAttribute..ctor()
	at System.RuntimeTypeHandle.CreateCaInstance(RuntimeType type, IRuntimeMethodInfo ctor)
	at System.Reflection.CustomAttribute.GetCustomAttributes(RuntimeModule decoratedModule, Int32 decoratedMetadataToken, Int32 pcaCount, RuntimeType attributeFilterType, Boolean mustBeInheritable, IList derivedAttributes, Boolean isDecoratedTargetSecurityTransparent)
	at System.Reflection.CustomAttribute.GetCustomAttributes(RuntimeType type, RuntimeType caType, Boolean inherit)
	at System.RuntimeType.GetCustomAttributes(Boolean inherit)
	at NUnit.Core.Reflect.GetAttributes(ICustomAttributeProvider member, Boolean inherit)
	at NUnit.Core.Reflect.HasAttribute(ICustomAttributeProvider member, String attrName, Boolean inherit)
	at NUnit.Core.Builders.NUnitTestFixtureBuilder.CanBuildFrom(Type type)
	at NUnit.Core.Extensibility.SuiteBuilderCollection.CanBuildFrom(Type type)
	at NUnit.Core.TestFixtureBuilder.CanBuildFrom(Type type)
	at NUnit.Core.Builders.TestAssemblyBuilder.GetFixtures(Assembly assembly, String ns)
	at NUnit.Core.Builders.TestAssemblyBuilder.Build(String assemblyName, Boolean autoSuites)
	at NUnit.Core.Builders.TestAssemblyBuilder.Build(String assemblyName, String testName, Boolean autoSuites)
	at NUnit.Core.TestSuiteBuilder.BuildSingleAssembly(TestPackage package)
	at NUnit.Core.TestSuiteBuilder.Build(TestPackage package)
	at NUnit.AddInRunner.NUnitTestRunner.run(ITestListener testListener, Assembly assembly, ITestFilter filter)
	at NUnit.AddInRunner.NUnitTestRunner.runMethod(ITestListener testListener, Assembly assembly, MethodInfo method)
	at NUnit.AddInRunner.NUnitTestRunner.MemberRun.Run(NUnitTestRunner testRunner, ITestListener testListener, Assembly assembly)
	at NUnit.AddInRunner.NUnitTestRunner.run(ITestListener testListener, Assembly assembly, IRun run)
	at NUnit.AddInRunner.NUnitTestRunner.RunMember(ITestListener testListener, Assembly assembly, MemberInfo member)
	at TestDriven.TestRunner.AdaptorTestRunner.Run(ITestListener testListener, ITraceListener traceListener, String assemblyPath, String testPath)
	at TestDriven.TestRunner.ThreadTestRunner.Runner.Run()

0 passed, 1 failed, 0 skipped, took 0.18 seconds (NUnit 2.5).</pre>

The test where the attributed class is instantiated has not been hit yet by NUnit.  Instead, it seems that NUnit is gleaning over the text fixtures in the assembly, finding all of the custom attributes, and instantiating those attributes before running any of the tests.  We ran into this issue because of an attribute that had an IoC container lookup (I know what you’re going to say, but we didn’t write it) in the constructor of the attribute. Due to the container not yet being initialized, an exception was thrown.

Hopefully Charlie Poole can provide some insight into this strange behavior on NUnit’s part.

Advertisements

1 Comment

Giles auto test runner for .NET v0.1.1.2 Released

Giles, my pet auto test runner project for .NET applications, has just grown up to v0.1.1.2 and been released on Nuget. Included in this release (and some previous releases) are:

v0.1.1.2 - Sept 12, 2011
 - Added x86 version of Giles to support testing of x86 target applications. A giles-x86.ps1 script has also been included to launch the x86 Giles instead of the AnyCPU version
 - Added Giles version output to help window and to the console window title

v0.1.1.1 - Sept 2, 2011
 - Added giles.ps1 & init script. Now instead of having to run Giles manually from the nuget package, you just need to run giles.ps1 from the root of the solution. Giles will find the correct .sln files and start watching straight away.
 - Added MIT license

v0.1.1.0 - Sept 1, 2011
 - Fixed Machine.Specification versioning issue where the test project needed to have the same version of mspec that Giles was using. Giles no longer has a dependancy on Machine.Specifications.dll, so we're version independent now!

v0.1.0.2 - Aug 29, 2011
 - Introduced Giles own runners instead of relying on console runners for each test framework
 - The only command line option required now is the solution file to watch, the test assembly is determined automatically.  If Giles gets the test assembly incorrect, then you can specify the test assembly location with the -t parameter

Most importantly with v0.1.1.0, through the magic of anonymous types and dynamics in .NET 4, Giles no longer cares which version of Machine.Specifications (mspec) you are running.  Check it out!

Also in the latest version, v0.1.1.2, Giles now ships with a good ol’ x86 version for your thunking pleasure.

Please post issues to the issue tracker on github.

Leave a comment

Getting the Castle Windsor Quartz.Net Facility logging with your log4net configuration

The Castle Windsor Quartz.Net facility is pretty awesome, it making getting Quartz.net up and running a fairly painless process.  However, when things go wrong, Quartz.net will be SILENT about it, unless you configure logging correctly.  Already having log4net and Castle.Facilities.Logging setup in my project, here’s the awesomeness that my team came up with to get Quartz.net to tell us what’s up:

Step number the 1) Add a reference to Common.Logging v2.0.0.0 and Common.Logging.Log4Net v2.0.0.0

Step number the 2) Add this assembly binding redirect to your app/web.config:


<dependentAssembly>

<assemblyIdentity name="Common.Logging" publicKeyToken="af08829b84f0328e" culture="neutral"/>

<bindingRedirect oldVersion="0.0.0.0-1.2.0.0" newVersion="2.0.0.0"/>

</dependentAssembly>

Step number the 3)  Add this section group to the <configSections> in your app/web.config:

</pre>
<sectionGroup name="common">

<section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging" />

</sectionGroup>
<pre>

Step number the 4) Add this xml block in the <configuration> block, right after <configSections> works good:

</pre>
<common>

<logging>

<factoryAdapter type="Common.Logging.Log4Net.Log4NetLoggerFactoryAdapter, Common.Logging.Log4net">

<arg key="configType" value="FILE-WATCH" />

<arg key="configFile" value="Log4Net.config" />

</factoryAdapter>

</logging>

</common>
<pre>

Step number the last) Profit.

Now, if you mess up your cron schedule, like

<cron-expression>15 0/1 * CAT * ?</cron-expression>

, you’ll get a beautiful error:


<pre>ERROR 03:14:26 Error scheduling jobs: Illegal characters for this position: 'CAT'
System.FormatException: Illegal characters for this position: 'CAT'
   at Quartz.CronExpression.StoreExpressionVals(Int32 pos, String s, Int32 type)
   at Quartz.CronExpression.BuildExpression(String expression)
   at Quartz.CronExpression..ctor(String cronExpression)
   at Quartz.CronTrigger.set_CronExpressionString(String value)
   at Quartz.CronTrigger..ctor(String name, String group, String jobName, String jobGroup, DateTime startTimeUtc, Nullable`1 endTime, String cronExpression)
   at Quartz.Xml.JobSchedulingDataProcessor.ProcessJobs(quartz data)
   at Quartz.Xml.JobSchedulingDataProcessor.ProcessInternal(String xml)
   at Quartz.Xml.JobSchedulingDataProcessor.ProcessFile(String fileName, String systemId)
   at Quartz.Xml.JobSchedulingDataProcessor.ProcessFileAndScheduleJobs(String fileName, String systemId, IScheduler sched, Boolean overwriteExistingJobs)
   at Quartz.Plugin.Xml.JobInitializationPlugin.ProcessFile(JobFile jobFile)

Many thanks to our friend Lane McLaughlin for figuring out this goo… everyone loves a log.

Leave a comment