mikeobrien.net Curriculum Vitae Blog Labs
Wednesday, November 18, 2009

Gallio is a fantastic test runner that integrates with a number of tools (like R#, which I love) and a ton of testing frameworks. So I set out to modify our NAnt build scripts to use Gallio instead of invoking tests directly with a particular testing framework. Here is how the setup went having NAnt invoke Gallio which invoked NUnit tests (Thanks to Rob Reynolds for his insights):

1) Download and unzip the Gallio distro from here: http://www.gallio.org/Downloads.aspx

2) Setup the NAnt tasks as follows (To run all tests in an assembly). You can get more info on the Gallio NAnt task properties here. The filter defaults to all but I included it for demo purposes. Error code 16 means that there were no tests. I ignored it since I was reorganizing the test suites and some didn’t have tests yet.

<loadtasks assembly="<GalioBinFolder>\Gallio.NAntTasks.dll" />
<gallio working-directory="<TestSuiteWorkingFolder>"
        report-types="Xml-Inline"
        report-directory="<ReportFolder>"
        report-name-format="gallio-results"
        show-reports="false"
        failonerror="false"
        verbosity="Normal"
        echo-results="false"
        filter="include *"
        result-property="GallioResult">
  <files>
    <include name="<PathToATestSuiteAssembly>"/>
  </files>
</gallio>
<fail if="${GallioResult != '0' and GallioResult != '16'}" >
One or more tests failed (Error code ${GallioResult}).
Please check the log for more details</fail>

3) Copy the \GallioBundle-x-x-x\bin\Resources folder to \CruiseControl.NET\webdashboard or another location that is accessible from the web.

4) Copy \GallioBundle-x-x-x\bin\Resources\xsl\*.xsl to \CruiseControl.NET\webdashboard\xsl. You don't actually need all those files, but who knows, that may change in the future.

5) Edit the Gallio-Report.ccnet-details.xsl and Gallio-Report.ccnet-details-condensed.xsl files in the \CruiseControl.NET\webdashboard\xsl folder from step 4. Set the cssDir, jsDir, imgDir variables to the url of the files you copied in step 3.

6) Open the \CruiseControl.NET\webdashboard\dashboard.config file and add the following entries:

<dashboard>
  ...
  <plugins>
    ...
    <buildPlugins>
      <buildReportBuildPlugin>
        <xslFileNames>
          ...
          <xslFile>xsl\Gallio-Report.ccnet-details-condensed.xsl</xslFile>
          ...
        </xslFileNames>
      </buildReportBuildPlugin>
      ...
      <xslReportBuildPlugin description="Test Summary" actionName="GallioSummary"
                  xslFileName="xsl\Gallio-Report.ccnet-details-condensed.xsl" />
      <xslReportBuildPlugin description="Test Details" actionName="GallioDetails"
                  xslFileName="xsl\Gallio-Report.ccnet-details.xsl" />
      ...
    </buildPlugins>
    ...
  </plugins>
</dashboard>

Unfortunately Gallio doesn't currently have an email friendly report stylesheet so you’ll have to roll your own.

Wednesday, November 18, 2009 10:50:03 PM (GMT Standard Time, UTC+00:00)  #   |  Comments [0]  |  Trackback
Monday, January 26, 2009

There are a number of ways to setup configuration in the development, pre production and production environments. One way is to maintain independent .config files for different environments\developers and manually copy the appropriate file to the target during deployment. I've never warmed up to this approach as it leads to obvious headaches like difficulty keeping the config files in sync (And making sure everyone is using the most up to date config file), its error prone, and there ends up being many different .config files. I have been using an approach that I really like where there is one and only one .config file for all developers and all environments (Per project of course). Developers all share the same config file and thus the same configuration. When the config file is deployed to different environments, like UAT, staging and production, the deployment NANT script modifies the configuration during deployment (I also enforce a separation between NAnt script and configuration so setting and maintaining of configuration is fairly trivial). When the config file is modified it is automatically propagated to developers when they do an update and the new schema is guaranteed to make it to all the different environments. Of course the down side to this approach is the same as the up side, every developer has the same configuration... It would really be a pain to force every developer to have a strictly defined development environment; there is no flexibility and leeway for developer preference. In order to get the best of both worlds I've been using the hosts file and symbolic links to "map" the common configuration to the developers configuration.  For example the default developer config (In source control) might look like this:

<configuration>
  <sweetApp>
    <paths>
      <mediaFiles localPath="C:\SweetCompany\SweetApp\SweetApp.Web.UI\MediaFiles"/>
      <uploads localPath="C:\SweetCompany\SweetApp\Uploads"/>
    paths>
    <web>
      <sites errorPage="/SystemMessage.aspx">
        <main domainName="www.dev.sweetapp.com" />
        <services domainName="services.dev.sweetapp.com" />
        <administration domainName="admin.dev.sweetapp.com" />
      sites>
    web>
  sweetApp>
  <system.net>
    <mailSettings>
      <smtp deliveryMethod="Network" from="admin@sweetapp.com">
        <network host="smtp.dev.sweetapp.com" port="25" 
                 defaultCredentials="true"/>
      smtp>
    mailSettings>
  system.net>
configuration>

The hosts file, under %SystemRoot%\system32\drivers\etc\, allows you to point a network name to an IP address (You probably already know this...). So in the example above the developer could add to the following to their host file.

# SweetApp Dev Mappings
127.0.0.1          www.dev.sweetapp.net
127.0.0.1     services.dev.sweetapp.net
127.0.0.1        admin.dev.sweetapp.net
127.0.0.1         smtp.dev.sweetapp.net

This will of course map all these network names the local machine. But if, for example, the developer wanted to run parts of their environment in a VM or on another box altogether they could easily point to the IP address elsewhere. If your creating a hosts file entry for your local SQL Server that you will be connecting to with Windows Auth, be sure to setup an alias as described here

The other issue is paths. I personally keep all my "data" (Including code) under a second partition. And I like to keep things under a particular folder structure. Others may not roll this way and have different path/drive preferences. Using directory junctions you can create a "shortcut" to another folder. This can be easily accomplished with the mklink command in Vista/Server 08 (In XP and earlier there is another utility in the resource kit but I forget the name.) For the above example you could issue the following command to create a junction that links to a working folder on the D: drive.

mklink /J c:\SweetCompany\SweetApp d:\Dev\WorkApps\SweetApp

This essentially maps the first folder to the second folder so the path references in the config file will be valid but the developer can work out of the path they choose. You can also accomplish something similar with the subst command (Which maps a drive letter to a path). The problem with this approach is that the mapping is only recognized in the current login session. An ASP.NET page, for example, will know nothing about it since its running under the logon session of its process identity.

Monday, January 26, 2009 11:05:32 PM (GMT Standard Time, UTC+00:00)  #   |  Comments [0]  |  Trackback
Monday, July 28, 2008

When setting the target framework (IE: nant -buildfile:some.nant -t:net-3.5) the Nant script fails with a null reference exception and the message "Microsoft .NET Framework 3.5 (net-3.5) is not installed, or not correctly configured." Looks like this is resolved in the latest nightly build found here.

Monday, July 28, 2008 8:51:55 PM (GMT Daylight Time, UTC+01:00)  #   |  Comments [0]  |  Trackback

If you are receiving the above error message it may be that you installed the NantContrib task binaries under the <nant>\bin\tasks\net\20 folder (As the NantContrib readme.txt specifies). Evidently the "tasks" folder is now called "extensions" in Nant 0.86. So the proper path would be <nant>\bin\extensions\net\20.

Monday, July 28, 2008 8:42:13 PM (GMT Daylight Time, UTC+01:00)  #   |  Comments [0]  |  Trackback
Wednesday, December 05, 2007

...and your 100% sure your xpath is correct. It's probably that there is a namespace defined. All you need to do is define the namespace under the xmlpoke element and prefix your element names with the namespace prefix. Check it:

web.config:

<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
  <appSettings>
    <add key="SomeSetting" value="" />
  </appSettings>
</configuration>

NAnt:

<project name="YadaYadaYada" default="default">
    <target name="default">
        <xmlpoke
             file="D:\Temp\web.config"
             xpath="/ns:configuration/ns:appSettings/ns:add[@key='SomeSetting']/@value"
             value="SomeValue">
                  <namespaces>
                      <namespace prefix="ns" uri="
http://schemas.microsoft.com/.NetConfiguration/v2.0" />
                  </namespaces>

         </xmlpoke>
   </target>
</project>

Wednesday, December 05, 2007 8:00:25 PM (GMT Standard Time, UTC+00:00)  #   |  Comments [2]  |  Trackback
Tuesday, November 27, 2007

Testing for the existence of a folder with NAnt is pretty simple. The only caveat is that you reference parameters without the ${} qualifier. You can also concatenate strings, building the path dynamically...

<property name="RootPath" value="D:\Temp" />
<property name="RelativePath" value="SomeFolder" />

<target name="default">

   <if test="${directory::exists(RootPath + '\' + SomeFolder)}">
      <echo message="${RootPath}\${SomeFolder} exists!"/>
   </if>

   <if test="${not(directory::exists(RootPath + '\' + SomeFolder))}">
      <echo message="${RootPath}\${SomeFolder} does not exist!"/>
   </if>

</target>

As a side note; if you plan on passing a path into an exec as a parameter remember to quote it with the &quot; HTML entity:

<exec program="${Subversion}">
    <arg value="update &quot;${RootPath}\${SomeFolder}&quot;" />
</exec>
 

Tuesday, November 27, 2007 5:43:06 PM (GMT Standard Time, UTC+00:00)  #   |  Comments [0]  |  Trackback

NAnt scripts can get cluttered with configuration values. One way to clean this up is to create properties that are used throughout the script. Although, you could take this a step further and store NAnt script configuration values in an Xml file. I like this approach because it creates a clear separation between the script and modifiable parameters. For example lets say we have an Xml configuration file called Build.config that contains paths:

<?xml version="1.0"?>
<buildConfiguration>
    <paths>
        <path name="SubversionPath" value="C:\Program Files\CollabNet Subversion Server\svn.exe" />
    </paths>
</buildConfiguration>

Our NAnt script could then load these paths into parameters in an "init" target. All other targets could then depend on this "init" target.

<?xml version="1.0"?>
<project name="Build" default="default">

    <property name="SubversionPath" value="" />
    <target name="init">
        <xmlpeek

              file="Build.config"
              xpath="/buildConfiguration/paths/path[@name = 'SubversionPath']/@value"
              property="SubversionPath"/>
     </target>

     <target name="default" depends="init">
         <echo message="${SubversionPath}" />

     </target>
</project>

image

Tuesday, November 27, 2007 5:09:38 PM (GMT Standard Time, UTC+00:00)  #   |  Comments [0]  |  Trackback
Monday, November 26, 2007

Passing parameters to a NAnt script from CruiseControl.NET is pretty simple. Basically you pass name/value pairs with the -D command line argument in the buildArgs element. Values with spaces need to be wrapped with double quotes. The following CC.NET configuration file illustrates this:

<cruisecontrol>
     <project>
          ...
          <tasks>
               <nant>
                    <executable>C:\Program Files\nant-0.85\bin\NAnt.exe</executable>
                    <baseDirectory>F:\Build\BabelFish2.0\WorkingFolder</baseDirectory>
                    <buildArgs>-D:param1=somevalue -D:param2="some other value"</buildArgs>
                    <nologo>true</nologo>
                    <buildFile>Source/Build/Build.nant</buildFile>
                    <targetList>
                         <target>default</target>
                    </targetList>
                    <buildTimeoutSeconds>2400</buildTimeoutSeconds>
               </nant>
          </tasks>
          ...
     </project>
</cruisecontrol>

The target NAnt script need only reference the properties as follows:

<project name="YadaYadaYada" default="default">
  <target name="default">
   
<echo message="Param 1=${param1}, Param 2=${param2}"/>
  </target>
</project>

image

Monday, November 26, 2007 10:32:34 PM (GMT Standard Time, UTC+00:00)  #   |  Comments [0]  |  Trackback
Wednesday, November 14, 2007

Evidently the aspnet_compiler.exe does not care about .refresh files (From my tests and what I read on the internets). It would be nice if it did as it would reduce the steps needed to build a WSP (Web Site Project) on a build server. I ended up just manually copying the assemblies into the bin folder before the build as follows in this nant script:

<mkiisdir dirpath="..\BabelFish.ServiceHost.Web" vdirname="BabelFish.ServiceHost.Web" />

<copy todir="..\BabelFish.ServiceHost.Web\bin">
    <
fileset basedir="..\BabelFish.Services\bin">
        <
include name="*.dll" />
    </
fileset>
</
copy>

<exec program="C:\WINDOWS\Microsoft.NET\Framework\${framework.version}\aspnet_compiler.exe" useruntimeengine="true">
    <
arg value="-p" />
    <
arg value="..\BabelFish.ServiceHost.Web" />
    <
arg value="-v" />
    <
arg value="BabelFish.ServiceHost.Web" />
</
exec>

<deliisdir vdirname="BabelFish.ServiceHost.Web" />

Wednesday, November 14, 2007 6:33:25 PM (GMT Standard Time, UTC+00:00)  #   |  Comments [0]  |  Trackback
Creative Commons License