I was having some difficulty the other day getting code coverage analysis to run on just the selected assemblies that I wanted in my build output directory, and had the requirement (placed on myself) that I didn’t want to list the assemblies declaratively in my build configuration file. Not finding a quick solution, I thought I’d share what I came up with – it may end up being helpful to someone out there. Please do let me know if you have another solution, I’d be interested in hearing about it.
As a background, I’m using the Gallio suite (including MbUnit) for testing which includes the Gallio test runner and custom Gallio NAnt task. I want to limit the list of covered assemblies to those meeting a set of criteria, not all assemblies in a given folder. The solution I came up with involves writing a custom NAnt function, which turns out to be a trivial thing to do.
First, a portion of my Gallio NAnt task:
<gallio runner-type="NCover"
working-directory="${build.output.dir}"
report-types="html"
report-directory="${test.output.dir}"
show-reports="false"
failonerror="true"
verbosity="Normal"
echo-results="true">
<runner-property value="NCoverArguments='//q //a ${coverage.assemblies}'"/>
Note the <runner-property /> element with NCoverArguments specified in the value attribute. In this attribute, I specify a list of NCover runtime arguments, including the //a argument used to specify the list of assemblies to be profiled delimited by semi-colons. The trick for me was to set this list dynamically. I also, quite frankly, didn’t want to get into writing a ton of NAnt code using things like string::concat functions and whatnot. Before I call this <gallio /> task in the appropriate target, I set a property like so:
<property name="coverage.assemblies"
value="${mlincek::getcoverageassemblies(assembly.output.dir)}"/>
Essentially, here, I have set the list of coverage assemblies (property/variable ‘coverage.assemblies’) by calling out to a custom-written NAnt function that I have written, and passing in my build output directory as a string. Note this function returns a string:
<!-- Custom NAnt functions -->
<script language="C#" prefix="mlincek">
<code>
<![CDATA[
[Function("getcoverageassemblies")]
public static string GetCoverageAssemblies(string assemblyFolder)
{
StringBuilder sb = new StringBuilder();
FileInfo[] files = (new DirectoryInfo(assemblyFolder)).GetFiles();
foreach (FileInfo file in files)
{
string fileName = file.Name;
if (!fileName.EndsWith(".Tests.dll") && !fileName.EndsWith(".pdb"))
{
int dllPosition = fileName.LastIndexOf(".dll");
sb.Append(fileName.Substring(0, dllPosition) + ";");
}
}
return sb.ToString();
}
]]>
</code>
</script>
You can place this script task at the bottom of your NAnt .build file, or do as I do and place it into a common.build file which you can reference via a NAnt <include /> task. Of course, this function could be made more flexible and used for other things besides setting the coverage assembly list simply by passing in the filter criteria either as a string parameter, an expression, or a collection of rules (rule objects), but hey, this is a build file for gosh sakes and it got the job done for me.
Now, I can add assemblies to my overall solution and as long as I follow my naming conventions for projects and build folder structure, they will be picked up automatically for coverage analysis, per my filter criteria.
Hope this helps someone!