Cumbayah! … continuously improving

18Dec/110

Testing Web.config Transformations, Part 1

Web.config transformations is a Microsoft-supported technology for adapting a base configuration to a particular deployment environment.

In my previous post, I mentioned how AppHarbor provides an online tool for manually testing transformations. Also, the Visual Studio extension SlowCheetah provides support for manually testing and transformation and diffing them against the base configuration from inside Visual Studio. These tools are useful and may be fine for tinkering with getting a transform just right, but being software professionals, we find it highly desirable to reliably automate our transformation testing.

In this article we shall look at one way to exercise our Web.config transformations from our own code, so that we can TDD our transformations and regression test them as part of our continuous integration cycle, like everything else.

Starting Point

The actual Web.config transformation process is normally invoked by MSBuild, as described in detail by Elton Stoneman in this post. (One approach to testing the transformations could, thus, be to fork off MSBuild processes on custom build targets, but we'd like to take a less cumbersome route.)

Being an MSBuild task means that the transformation is implemented as an ITask realization. The concrete implementation is called TransformXml and resides in the Microsoft.Web.Publishing.Tasks.dll assembly. Note that this assembly is not pre-installed with Windows/.NET but rather installed along with Visual Studio 2010, typically to C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v10.0\Web). Given that its location is not fixed, you'd probably want to add it to your dependencies folder (caveat emptor: check license terms before passing it around).

From Elton's post, we can also see 3 arguments being passed to the TransformXml task:

  • Source (the XML file to transform)
  • Transform (the transformation XML file itself)
  • Destination (the target of the transformed XML file)

Armed with that background knowledge, let's move on and try to get the transformation to run outside of the MSBuild environment in which it normally executes. For our experiment, we'll be transforming the following source (app.config):

<configuration>
  <appSettings>
    <add key="testKey" value="testValue"/>
  </appSettings>
</configuration>

The transformation we'll be testing (app.Debug.config) should change the "testValue" of the "testKey" to something transformation specific ("debugValue" in this case):

<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <appSettings>
    <add key="testKey"
         value="debugValue"
         xdt:Locator="Match(key)" xdt:Transform="SetAttributes" />
  </appSettings>
</configuration>

Test Approach

Our initial stab at automatically testing the transformation will be to invoke the XmlTransform through its outermost interface; i.e. the ITask interface. First, we need to add a reference to Microsoft.Web.Publishing.Tasks.dll as mentioned above. Additionally, we need to reference Microsoft.Build.Framework and Microsoft.Build.Utilities.v4.0 (these should be available from your usual Project, Add Reference, .NET tab).

Opening up the XmlTransform class in Visual Studio's Object Browser shows us a single method, Execute(), plus a handful of properties, most of which we recognize from Stoneman's previously mentioned post. A little experimentation reveals the following details:

  • SourceRootPath
    • The base path to which the XML file specified in Source is relative. If not specified, this will  be the current directory (which, when running from a test runner, would typically be your build target directory).
  • Source
    • The name of the XML source file to be transformed. If this is not an absolute path, it will be relative to the SourceRootPath.
  • TransformRootPath
    • The base path to which the XML file specified in Transform is relative. If not specified, this will be the same as SourceRootPath.
  • Transform
    • The name of the XML transformation to be applied to the source. If this is not an absolute path, it will be relative to the TransformRootPath.
  • Destination
    • The name of the resulting transformed XML file. If this is not an absolute path, it will be relative to the current directory (typically your build target directory, in a test context).

So all we need to do is something like the following, right?

var transformer = new TransformXml
{
   SourceRootPath = @"..\..\..\Cumbayah.WebTransformTesting\",
   Source = @"app.config",
   Transform = @"app.Debug.config",
   Destination = @"app.transformed.config"
};
var transformationSucceeded = transformer.Execute();

Well, as the System.InvalidOperationException with the message "Task attempted to log before it was initialized" thrown in our face subtly tells us, this is not quite the case...

Investigating the task implementation with Reflector shows the problem to be due to the fact that a property, BuildEngine, of the Task base class has not been set. It is used as a dependency injection mechanism, giving the task access to some environment services (see IBuildEngine on MSDN) normally provided by MSBuild.

Well, the whole purpose of this experiment was to run outside of MSBuild, so we'll roll our own "build engine" instead:

public class BuildEngineForTest : IBuildEngine
{
   public void LogErrorEvent(BuildErrorEventArgs e)
   {
      Console.WriteLine(LogFormat, "ERROR", e.Message);
   }

   public void LogWarningEvent(BuildWarningEventArgs e)
   {
      Console.WriteLine(LogFormat, "WARNING", e.Message);
   }

   public void LogMessageEvent(BuildMessageEventArgs e)
   {
      Console.WriteLine(LogFormat, e.Importance, e.Message);
   }

   public void LogCustomEvent(CustomBuildEventArgs e)
   {
      Console.WriteLine(LogFormat, "CUSTOM", e.Message);
   }

   public bool BuildProjectFile(
      string projectFileName, string[] targetNames,
      IDictionary globalProperties, IDictionary targetOutputs)
   {
      return true;
   }

   public bool ContinueOnError
   {
      get { return true; }
   }

   public int LineNumberOfTaskNode
   {
      get { return 0; }
   }

   public int ColumnNumberOfTaskNode
   {
      get { return 0; }
   }

   public string ProjectFileOfTaskNode
   {
      get { return string.Empty; }
   }

   private const string LogFormat = "{0} : {1}";
}

This was the piece we were missing to be able to run the transformation outside MSBuild. We are, thus, able to write our coveted test:

...
   [Test]
   public void ApplyingDebugTransformationToConfigurationSetsExpectedKey()
   {
      const string targetFile = @"app.transformed.config";

      var transformer = new TransformXml
      {
         BuildEngine = new BuildEngineForTest(),
         SourceRootPath = @"..\..\..\Cumbayah.WebTransformTesting\",
         Source = @"app.config",
         Transform = @"app.Debug.config",
         Destination = targetFile
      };

      // Left as exercise for the reader... ;]
      AssertKeyInTransformedFile(targetFile);
   }
...

Our transformation runs successfully, yielding the following console output from our "build engine":

Normal : Transforming Source File: ..\..\..\Cumbayah.WebTransformTesting\app.config
Normal : Applying Transform File: ..\..\..\Cumbayah.WebTransformTesting\app.Debug.config
Low : Executing SetAttributes (transform line 6, 35)
Low : on /configuration/appSettings/add[@key='testKey']
Low : Applying to 'add' element (source line 4, 6)
Low : Set 'key' attribute
Low : Set 'value' attribute
Low : Set 2 attributes
Low : Done executing SetAttributes
Normal : Output File: app.transformed.config
Normal : Transformation succeeded

Yay? Yay.

Conclusion

We have shown how Web.config Transformations can be invoked programmatically, outside the MSBuild process by providing a simple custom IBuildEngine realization; a technique that might prove useful for other scenarios as well.

We have applied that knowledge to enable automated testing of our transformations.

In my next post in this series, we'll continue our investigation, aiming to get rid of the whole, nasty detour around the file system.

14Nov/110

Web.config Transformations and XML Namespaces

In the project I'm currently working on, we use Web.config transformations to adapt a base configuration to a particular deployment environment.

I was adding a transformation to adapt our NLog configuration for the various environments, when I ran into a problem. I'd added the following transformation, intended to set the log level to Warning on the given deloyment:

<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd">
  <rules>
    <logger name="*" minlevel="Warn"
      xdt:Transform="SetAttributes(minlevel)" xdt:Locator="Match(name)"
    />
  </rules>
</nlog>

However, the original fragment kept being passed through without modification. I tried various modes of matching (incidentally, the online Web Config Transformation Tester is a good tool for such tinkering with transformations) to no avail, until I decided to try and remove the NLog namespace from the transformation:

<nlog>
  <rules>
    <logger name="*" minlevel="Warn"
      xdt:Transform="SetAttributes(minlevel)" xdt:Locator="Match(name)"
    />
  </rules>
</nlog>

And lo and behold; it worked.

That could have been the end of it, but I decided to dig a bit deeper, since we have the NLog schema in our solution and properly referencing it allows us to have IntelliSense within the web.config.

Eventually, as an experiment, I tried associating a namespace prefix with the NLog namespace at the top level, and then explicitly qualify the NLog elements with that prefix, rather than relying on specifying a default namespace on the NLog element:

<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform"
  xmlns:nlog="http://www.nlog-project.org/schemas/NLog.xsd"
>

  <nlog:nlog>
    <nlog:rules>
      <nlog:logger name="*" minlevel="Warn"
        xdt:Transform="SetAttributes(minlevel)" xdt:Locator="Match(name)"
      />
    </nlog:rules>
  </nlog:nlog>

</configuration>

Success!

Conclusion

Explicitly prefixing elements from foreign XML namespaces in config transformations must be described as a workaround, since semantically the first and the third NLog fragments are equivalent.

In spite of this flaw in the web.config transformations implementation, this workaround allows you to get the benefits of IntelliSense and better validation when you have the schema for NLog or any other foreign fragment available.

23Jul/110

Custom Danish keyboard for Windows, based on US keyboard but with AltGr mappings of national characters.

For quite some time, I've felt impeded by the Danish keyboard layout when programming, but have avoided switching to US layout because of the regularity with which I still have to type the special Danish characters and other European accents.

As I started to use my laptop more and more for development, the impediment grew to the point where I finally decided to do something about it. I downloaded the Microsoft Keyboard Layout Creator and created a custom Danish keyboard for Windows, based on US keyboard but with intuitive Alt-Gr (or Ctrl-Alt) mappings of national characters and accents. You can see the resulting keyboard layout here - and download the keyboard here.

OK, but ... why?!

First of all, this probably won't make sense to you at all, unless you do a lot of development work in Denmark/on a Danish keyboard. The thing is, most of the delimiting characters used over and over when coding (such as []{}<>;\^) are mapped to incredibly awkward, wrist breaking Alt-Gr+numeric keys on the Danish keyboard layout.

Why would you want to go through the trouble of installing a custom keyboard, though? Since Windows supports multiple keyboards, can't you just switch between them?

Well, yeah, you could. Windows even maintains language settings per process, so you could have the Danish keyboard active in, say, Outlook while having the US keyboard active in, say, Visual Studio for coding.

I tried that for a while and got totally schizophrenic. First of all, it's annoying to have to set the language for the application every time you start it (shame on Windows for now allowing you to configure this per application), even with hotkeys to do so. Secondly, jumping between 2 different layouts all the time is really confusing - at least it was to me.

I wanted to have one layout active all the time, primarily based on the US keyboard to facilitate coding (my primary activity), but still with relatively easy access to the Danish national characters (æøåÆØÅ) and accents at an intuitive location on the keyboard (i.e. their regular physical key on the DK layout), for use when writing mails, instant messaging, etc.

The result is this keyboard. It does the trick for me at least, and if it can save your wrists some pain as well, go right ahead and get it. It can be installed on all recent versions of Windows (tested on XP and 7 on both 32 and 64 bit versions). Once installed, you can select Danish - USDK as Default Input Language in Regional/Language settings in the Control Panel, and off you go.

Enjoy!

Tagged as: , No Comments
28Oct/102

NCover 3.4 hangs after running NUnit 2.5.8 tests.

Recently, I've had issues with NCover. More precisely, NCover Complete, v3.4.12.6869 x64, consistently started to hang after having executed the test suite it was supposed to report on. NCover Explorer simply said "Starting NCover..." and showed a Busy cursor that never went away.

I reproduced the same behaviour on NCover.Console - after running the NUnit tests, the process hangs forever, and cannot be terminated with CTRL+C/Break either.

After spending quite a while trying to find out what was going on, I realized I'd recently upgraded NUnit to 2.5.8 (.10295 to be specific). I tried to downgrade to 2.5.7.10213 and lo and behold, the problem went away... :o

A few additional details, that may or may not be relevant to the issue (some of you may help confirm/reject the issue on other environments): I'm running on an x64 system and the solution is built on the .NET 4 framework.

I've reported the issue to the NCover people - in the mean time, knowing this workaround may save you some time, if you face the same issue and Google takes you here... :)

Update, 2010-11-01:

Since I put in the support ticket with NCover, I got good and rapid feedback. They were able to reproduce it and investigated further, finding it to be a issue with NUnit 2.5.8 ifself (confirmed with one of the NUnit developers). The issue is apparently proving tricky to deal with, and has not been corrected yet. The bug, David from NCover informs me, specifically addresses termination of an nunit-agent process, which only happens when NUnit is trying to run tests in a separate process, which is how NCover integrates.

Tagged as: , , , 2 Comments
1Feb/100

Separation of Concerns

Applying one of the central pillars of good software design, I've decided to apply Separation of Concerns to my blog as well. The blog that you're now reading represents my professional interests solely. You'll find technical and methodological articles related to software development here, whereas my personal posts are kept on a personal blog elsewhere.

Tagged as: No Comments