Friday, June 6, 2014

Continous Installation : An historical overview

A couple of months ago I posted an update on the installation method I use at work. You can read more about that here
Since those numbers have grown tremendously, I decided to log every install request.
Sadly I do not have the data from the beginning in 2009, only from 2011 onwards.

Since a picture says a thousands words here's a graph :

As you can see, in 2013 there was an explosion of installations, one of the reasons I did not have that much time for CCNet.

I just hope this year the amount of installations is lower again.

We now have about 110 customers, so that's about seven times as much as back in 2009 !

Tuesday, June 3, 2014

Weird problem after upgrading CCNet to .Net 4.5

The problem is a bit weird : after upgrading CCNet to .Net framework 4.5 one test fails, and I do not know what the problem is :-(
  1. is it a breaking change in the .Net framework
  2. is it a different behavior of Nunit (I upgraded Nunit also to the latest version to be able to test .Net 4.5)
  3. something else ...
On using the new NUnit on the 1.8.5 tests, all tests are also green. Below is the test, the bold line is the one that fails, it's one from the dashboard, IO namespace.
        [Test]
        public void NotAvailableNotEvenEqualToItself()
        {
            Assert.AreNotEqual(ConditionalGetFingerprint.NOT_AVAILABLE, ConditionalGetFingerprint.NOT_AVAILABLE);
            Assert.AreSame(ConditionalGetFingerprint.NOT_AVAILABLE, ConditionalGetFingerprint.NOT_AVAILABLE);
        }
As said before that one works in .Net 3.5, but not in .Net 4.5
I've updated the test to the following and still the same bold line breaks.
        [Test]
        public void NotAvailableNotEvenEqualToItself()
        {
            Assert.IsTrue(ConditionalGetFingerprint.NOT_AVAILABLE == ConditionalGetFingerprint.NOT_AVAILABLE);
            Assert.IsTrue(ConditionalGetFingerprint.Equals(ConditionalGetFingerprint.NOT_AVAILABLE, ConditionalGetFingerprint.NOT_AVAILABLE));

            Assert.AreNotEqual(ConditionalGetFingerprint.NOT_AVAILABLE, ConditionalGetFingerprint.NOT_AVAILABLE);
            Assert.AreSame(ConditionalGetFingerprint.NOT_AVAILABLE, ConditionalGetFingerprint.NOT_AVAILABLE);
        }
The same test with the added asserts is still green in .Net 3.5 (with both versions of NUnit).

The code in CCNet uses statements like : (if fingerprint1 == fingerprint2) as far as I could see, so I would guess that the functionality stays the same, but I'm not 100% sure :-(

I understand the difference between the == and the .equals operator
  • == tests for object equivalence
  • .Equals() test that objects represent the same 'value', they may represent different objects but the value of these are considered the same.
That ConditionalGetFingerprint does an override of the Equals method, so that's why the test is there, and it breaks after upgrading.

And now something really weird : If I change the test to just the breaking assert

        [Test]
        public void NotAvailableNotEvenEqualToItself()
        {
            Assert.AreNotEqual(ConditionalGetFingerprint.NOT_AVAILABLE, ConditionalGetFingerprint.NOT_AVAILABLE);
        }
and I change that override equals of the ConditionalGetFingerprint into the following :
        public override bool Equals(object obj)
        {
            Console.WriteLine("Equals method");
            return false;
         }
Result :
  1. In .Net framework 3.5 it passes, and I see the string "Equals method" in the output.
  2. In .Net framework 4.5 it does not pass, and I do NOT see the string "Equals method" in the output
?????????????
--> this leads me to the believe that this is change in the .Net framework or it's compiler. I've found a blog, and at the bottom it says :
In C# 4.0, if the operands to == are "dynamic" then we will do the usual compile-time analysis at runtime based on the runtime types, which effectively does give you something like double-virtual dispatch, at the cost of running the compiler at runtime. We will cache the results of the analysis, so the second time you hit the call site, it should be reasonably efficient.

so does this indeed imply a change in .Net? Any help, guidance, tips are really appreciated !

You can find the source of CCNet in the links below :

Monday, July 8, 2013

Continous Installation : An overview

Way back in 2009 I made a post that my company also uses CCNet for Continuous installation, you can read that one here : here. A quick overview, at that time we had
5 programs, and only used a WPF front end, WCF services and a database. And these had to be installed at 15 of our customers, meaning actually doing 75 installations.
Manual installation of 1 program took 20 minutes so this meant 75 * 20 = 1500 minutes which equals to 25 hours. So a good 3 days of work just installing !

Now we increased the amount of programs, the type of them and the amount of customers. The type of programs to install :
  • wpf click once
  • wcf services
  • windows services
  • VSTO addins
  • ms sql reports
  • web sites
And some customers use Terminal Services, which means they do not want to use click-once, but just xcopy deployment. Meaning all the assemblies of the front-end must be copied to a share on the terminal server(s).
Yes that is plural, we even a a customer that has 26 Terminal Servers !
Off course the corresponding databases need to be created and upgraded, the IIS needs to be configured, backups taken, folders created and cleared, ...
All that maintenance per server is also done via CCNet.
Installing a new version at a customer is very easy, upload the files to an ftp server, update and upload the customers CCNet.config.
All this updating is done via a program I wrote, not manually editing the config :-)
Below is an overview of 2012 :
programAmount of classic installs
dmnstrt680
Brgrzkn\Bvlkng2680
Brgrzkn\BrgrljkStnd637
Brgrzkn\BrgrljkStndRprtng268
Cmmn416
Fnncn\Bkhdng\BBC1259
Fnncn\Bkhdng\BBCBstlbnRprtng573
Fnncn\Bkhdng\BBCRprtng2540
Fnncn\Bkhdng\BBCStdRprtng4
Fnncn\Bkhdng\Ngb693
Fnncn\Bkhdng\NGBRprtng3
Fnncn\Bkhdng\NbRprtng2
Fnncn\Bkhdng\Schbrck1
prtns168
Rmmcmxtrn\Fnncn\Bkhdng\Bbc264
Rmmcmxtrn\Fnncn\Bkhdng\Ngb1
Rp\BnkTrmnl34
Rp\DcmntBhr793
Rp\Dcmntn470
Rp\d1
Rp\GbrkrsBhr1082
Rp\GbrkrsBhrRprtng91
Rp\GsSrvc38
Rp\PrcsBwkngRprtng26
Rp\PrcsMngr135
Rp\Rpprtn382
Rp\Rfrnts1034
Rp\RprtSrvrMngmntRprts5
Rp\Strtpgn862
Rl\Rmtljkrdnng119
Rl\RmtljkrdnngRprtng59
Scrtrt\MtngNT50


This is an total of 15370 installations !

And this is the downside of the story, it goes way to easy and fast :-)

Doing this the old-fashioned way would take 307400 minutes which are 5123 hours. Roughly 128 weeks (40 hour week)
just keeping the 20 minute per install, which is very fast for a manual install. And this does not take into account the new type of programs, copying them to X terminal servers, ....

The new way took me 4010 minutes which is 66 hours or a week and a half. And this is totally automatically, so no errors done on replacing settings and the like.

And because there are only 52 weeks in a year, .... this is a HUGE timesaver.

Sunday, May 27, 2012

CCNet 1.7 out soon

CCNet 1.7 is due out soon. A lot of work has gone into updating the wiki and incorporating work from contributors.

Anyone wanting to give the 1.7 a spin, please do. All comments are welcome.

An excerpt from the 1.7 release notes :

Backwards compatibility issues

There were a few items that broke when upgrading to 1.5 or 1.6, sorry for that. Here's a list of those that are known to be fixed
  • Nant : newline in causes "Target ' ' does not exist in this project."
  • Git : Merge Commits in GIT are being ignored by CCnet, causing "No modifications detected."
  • CCTray : Prevent Interval Trigger from modifying cctray detail column..
  • CCTray : app balloon shows always report builds even if it is set only to show warnings or errors.
  • BuildPublisher : KeepLastXBuilds broken in 1.7

What's different, needs some attention:

  • Configuration_Preprocessor, it's best that you start every included file must with :
  • if you used git, the first build with CCNet 1.7 will list ALL changes done. This is due to the fix of the Git repository. Maybe it's best to delete the first build artifact, and clean up the history.xml file for each project with Git. Or take a backup of the history.xml files, upgrade to CCNet 1.7, and place the backup's back.

Main new features

  • xml highlighter for the BuildLog and ProjectConfiguration
  • Plastic SCM 4.0 plugin
  • Updating the dashboard activity status automatically (refreshinterval needs to be set in dashboard.config)
  • add xsl for ms-test and mstest coverage for vs2010
  • new modification filter : Multi filter
  • show description of the project in dashboard and CCtray
  • new xslt Task : allows to do XSL transformation during the build
  • Dashboard admin page has a better look, with tooltips and the packages divided in sections
  • added a couple of packages for existing xsl files, making it easier to use them.

Tuesday, May 15, 2012

I'm back

It's been a while, but I'm back.
Now focusing on getting 1.7 out of the door, due end of June 2012.

For those who do not know, there is the new site :
CCNet site

What's new in 1.7 :
  • xml highlighter for the BuildLog and ProjectConfiguration
  • Plastic SCM 4.0 plugin
  • Updating the dashboard activity status automatically
  • add xsl for ms-test and mstest coverage for vs2010
  • new modification filter : Multi filter
  • show description of the project in dashboard and CCtray

The most annoying bugs fixed :
  • Nant : newline in causes "Target ' ' does not exist in this projechttp://www.blogger.com/img/blank.gift."
  • Git : Merge Commits in GIT are being ignored by CCnet, causing "No modifications detected."
  • CCTray : Prevent Interval Trigger from modifying cctray detail column..
  • CCTray : app balloon shows always report builds even if it is set only to show warnings or errors.
  • BuildPublisher : KeepLastXBuilds broken in 1.7


For a complete overview look at the roadmap :
Roadmap CCNet 1.7

Monday, September 26, 2011

Setting up CCNet in combination with VS2010

In this post I'll describe how one can set up CCNet to work with VS2010. Not everyone has the full blown version of TFS at their disposal.

Scenario setup

I always use the following setup at work :
  • Project_CI : for Continuous Integration (compile, unit-test)
  • Project_MakePackage : this makes the install package (compile, unit-test,integration-test, make package)
  • Project_QA : this does unit-test, integration_test, coverage and code analysis.
This is a pragmatic approach: a 'fix' can be deployed even when for example coverage is still below X percent, as long as all tests are passed. It's convention that QA must be fixed ASAP!

The CI must be as fast as possible, so it runs only the unit-tests. The CI project has an interval trigger checking the repo every 5 minutes. This project does NOT label TFS.

The makePackage project has a schedule trigger : every day at 20:00, and a labeler so we can easily branch via a label.

The QA project also has a schedule trigger : every day at 21:00. This project does NOT label TFS.

Step 1 : Setting up the source control part


In Tfs itself I have the following layout :

ProjectName
\__Main
| \Lib
| \Src
\__Releases
\__1_0_0_3450
| \Lib
| \Src
\__1_0_1_5678
\Lib
\Src
This allows for easy branching.

Step 2 : Setting up CCNet.config

I use the pre-processor to reduce a lot of the configuration. This allows me to define a CCNet project in just 30 lines!
You can read the full configuration for the tfs source control at the wiki Tfs Source Control
My advise : set the deleteworkspace and cleancopy to true, this prevents a lot of problems.
A full example of ccnet.config with comparable layout is at the bottom of this post.
The preprocessor declaration :
<cb:define name="vsts_ci">
<server>http://tfs-server:8080/tfs/default/</server>
<username>cruise</username>
<password>**********</password>
<domain>tfs-server</domain>
<autoGetSource>true</autoGetSource>
<cleanCopy>true</cleanCopy>
<force>true</force>
<deleteWorkspace>true</deleteWorkspace>
</cb:define>

The source control block inside a project :
<sourcecontrol type="vsts">
<workspace>$(ProjectName)</workspace>
<project>$/$(ProjectName)/Main</project>
<cb:vsts_ci/>
</sourcecontrol>

Step 3 : Setting up the build script


The main action lays of course in the build script, for which I use Nant. The reason I (still) use Nant is that I know it rather well, and it works. For compiling I just call the MSBuild task from NantContrib pointing to the VS2010 solution, but all other logic is in Nant.
An example of the Nant build script is also at the bottom of this post.

Step 4 : Testing with Ms-test


Like I said in the beginning, I have 2 kind of tests, UnitTests and Integration Tests. In MS-test I create a test-list with the name UnitTests directly under the root item. All tests in this list, and in test-lists beneath it will be ran when I specify UnitTests. The 'Integration Tests' (slow running ones, going to the database, ...) are in a test-list named IntegrationTests also directly under the root item. Here's an example of calling MS-Test via nant :
<exec program="${mstest_exe}">
<arg value="/testmetadata:${mstest_metadatafile}" />
<arg value="/resultsfile:MStest_Results.xml" />
<arg value="/testlist:UnitTests" />
<arg value="/testlist:IntegrationTests" if="${CCNetBuildCondition=='ForceBuild'}" />
</exec>


Step 5 : Using Ms-Test with coverage


In Ms-test you can specify that you also want coverage to run, see for setting it up.
I just made a company rule that for code coverage to be ran via ccnet, the testsettings file must be named : CodeCoverage.testsettings,
with a specific base name(cover_me) and no timestamps appended. Just to make things easier for me.
If you want MS-Test to run coverage, just pass the testsettings as an extra argument :
<exec program="${mstest_exe}" failonerror="false" resultproperty="testresult.temp" >
<arg value="/testmetadata:${mstest_metadatafile}" />
<arg value="/resultsfile:MStest_Results.xml" />
<arg value="/testsettings:CodeCoverage.testsettings" />
<arg value="/testlist:UnitTests" />
<arg value="/testlist:IntegrationTests" if="${CCNetBuildCondition=='ForceBuild'}" />
</exec>


There is a catch : Ms-Test from VS2010 does not produce XML anymore, see this post for a solution. You really need the dll from VS2008 for it to work, the VS2010 has another interface sadly enough. So best to digg up you DVD of VS2008. I've updated that program a bit so that is also removes the Lines from the coverage result file, making it a lot smaller to merge. Below is my source code (its VB.Net)

Showing the results


I've added 2 new xsl files (MsTestReport2010.xsl and MsTest2010Cover.xsl) to CCNet, you can use these in the dashboard in the build plugins.
<buildPlugins>
...
<xslReportBuildPlugin description="Ms Test" actionName="MSTest" xslFileName="xsl\MsTestReport2010.xsl" />
<xslReportBuildPlugin description="MS Test Coverage" actionName="MSTest2008Cover" xslFileName="xsl\MsTestCover2010.xsl"/>
...
</buildPlugins>





Attachments


CCNet.config

<cruisecontrol xmlns:cb="urn:ccnet.config.builder">
<!-- preprocessor settings -->
<cb:define WorkingDir="D:\WorkingFolders\" />
<cb:define WorkingMainDir="D:\ArtifactFolders\" />
<cb:define ArtifactsDir="\Artifacts" />

<cb:define name="vsts_ci">
<server>http://tfs-server:8080/tfs/default/</server>
<username>cruise</username>
<password>**********</password>
<domain>tfs-server</domain>
<autoGetSource>true</autoGetSource>
<cleanCopy>true</cleanCopy>
<force>true</force>
<deleteWorkspace>true</deleteWorkspace>
</cb:define>

<cb:define name="vsts_package">
<server>http://tfs-server:8080/tfs/default/</server>
<username>cruise</username>
<password>**********</password>
<domain>tfs-server</domain>
<autoGetSource>true</autoGetSource>
<cleanCopy>true</cleanCopy>
<force>true</force>
<applyLabel>true</applyLabel>
<deleteWorkspace>true</deleteWorkspace>
</cb:define>

<cb:define name="common_publishers">
<merge>
<files>
<file>Coverage.xml</file>
<file>MStest_Results.xml</file>
<file>simian.xml</file>
</files>
</merge>
<xmllogger />
<statistics />
<modificationHistory onlyLogWhenChangesFound="true" />
<artifactcleanup cleanUpMethod="KeepLastXSubDirs" cleanUpValue="2" />
<artifactcleanup cleanUpMethod="KeepLastXBuilds" cleanUpValue="25000" />
<email from="CruiseControl@TheBuilder.com"
mailhost="TheMailer.Company.com"
includeDetails="TRUE">
<groups/>
<users/>
<converters>
<ldapConverter domainName="Company" />
</converters>
<modifierNotificationTypes>
<NotificationType>Failed</NotificationType>
<NotificationType>Fixed</NotificationType>
</modifierNotificationTypes>
</email>
</cb:define>

<cb:define name="nant_common">
<executable>c:\Tools\nant\bin\nant.exe</executable>
<nologo>true</nologo>
<buildTimeoutSeconds>1800</buildTimeoutSeconds>
<buildArgs>-D:useExtraMsbuildLogger=true -D:isCI=true -listener:CCNetListener,CCNetListener -D:configuration=Debug</buildArgs>
</cb:define>

<cb:define name="nant_package">
<executable>c:\Tools\nant\bin\nant.exe</executable>
<nologo>true</nologo>
<buildTimeoutSeconds>1800</buildTimeoutSeconds>
<buildArgs> -D:useExtraMsbuildLogger=true -D:CreateInstallZips=true -listener:CCNetListener,CCNetListener -D:configuration=Release</buildArgs>
</cb:define>

<cb:define name="nant_qa">
<executable>c:\Tools\nant\bin\nant.exe</executable>
<nologo>true</nologo>
<buildTimeoutSeconds>3600</buildTimeoutSeconds>
<buildArgs>-D:useExtraMsbuildLogger=true -listener:CCNetListener,CCNetListener -D:configuration=DebugCA</buildArgs>
</cb:define>

<cb:define name="nant_target_CI">
<targetList>
<target>clean</target>
<target>compile</target>
<target>test</target>
</targetList>
</cb:define>

<cb:define name="nant_target_qa">
<targetList>
<target>clean</target>
<target>simian</target>
<target>compile</target>
<target>cover</target>
</targetList>
</cb:define>

<cb:define name="nant_target_package">
<targetList>
<target>clean</target>
<target>compile</target>
<target>test</target>
<target>make_package</target>
<target>makehelp</target>
</targetList>
</cb:define>
<!-- end preprocessor settings -->


<!-- Projects -->
<cb:scope ProjectName="ProjectX">
<cb:define ProjectType="_CI" />
<project name="$(ProjectName)$(ProjectType)" queue="Q1" queuePriority="901">
<workingDirectory>$(WorkingDir)$(ProjectName)$(ProjectType)</workingDirectory>
<artifactDirectory>$(WorkingMainDir)$(ProjectName)$(ProjectType)$(ArtifactsDir)</artifactDirectory>

<labeller type="defaultlabeller" />

<sourcecontrol type="vsts">
<workspace>$(ProjectName)</workspace>
<project>$/$(ProjectName)/Main</project>
<cb:vsts_ci/>
</sourcecontrol>

<tasks>
<nant>
<cb:nant_common/>
<cb:nant_target_CI />
</nant>
</tasks>

<publishers>
<cb:common_publishers />
</publishers>

</project>
</cb:scope>

<cb:scope ProjectName="ProjectX">
<cb:define ProjectType="_Package" />
<project name="$(ProjectName)$(ProjectType)" queue="Q1" queuePriority="801">
<workingDirectory>$(WorkingDir)$(ProjectName)$(ProjectType)</workingDirectory>
<artifactDirectory>$(WorkingMainDir)$(ProjectName)$(ProjectType)$(ArtifactsDir)</artifactDirectory>

<labeller type="defaultlabeller">
<prefix>1.0.1.</prefix>
<incrementOnFailure>false</incrementOnFailure>
</labeller>

<sourcecontrol type="vsts">
<workspace>$(ProjectName)</workspace>
<project>$/$(ProjectName)/Main</project>
<cb:vsts_package/>
</sourcecontrol>

<tasks>
<nant>
<cb:nant_package/>
<cb:nant_target_package />
</nant>
</tasks>

<publishers>
<cb:common_publishers />
</publishers>

</project>
</cb:scope>

<cb:scope ProjectName="ProjectX">
<cb:define ProjectType="_QA" />
<project name="$(ProjectName)$(ProjectType)" queue="Q1" queuePriority="801">
<workingDirectory>$(WorkingDir)$(ProjectName)$(ProjectType)</workingDirectory>
<artifactDirectory>$(WorkingMainDir)$(ProjectName)$(ProjectType)$(ArtifactsDir)</artifactDirectory>

<labeller type="defaultlabeller" />

<sourcecontrol type="vsts">
<workspace>$(ProjectName)</workspace>
<project>$/$(ProjectName)/Main</project>
<cb:vsts_package/>
</sourcecontrol>

<tasks>
<nant>
<cb:nant_common/>
<cb:nant_target_qa />
</nant>
</tasks>

<publishers>
<cb:common_publishers />
</publishers>

</project>
</cb:scope>

</cruisecontrol>


Nant Build Script

<project default="help">
<property name="solution" unless="${property::exists('solution')}" value="ProjectX.sln" />
<property name="configuration" unless="${property::exists('configuration')}" value="Debug" />
<property name="CCNetListenerFile" unless="${property::exists('CCNetListenerFile')}" value="listen.xml" />
<property name="msbuildverbose" unless="${property::exists('msbuildverbose')}" value="normal" />
<property name="CCNetLabel" unless="${property::exists('CCNetLabel')}" value="0.0.0.0" />

<property name="mstest_metadatafile" value="ProjectX.vsmdi" />

<property overwrite="false" name="Simian_exe" value="c:\Tools\simian\bin\simian-2.3.32.exe" />
<property overwrite="false" name="msbuildlogger" value="C:\Program Files\CruiseControl.NET\server\MSBuildListener.dll" />
<property overwrite="false" name="versionInfofile" value="VersionInfo.cs" />
<property overwrite="false" name="mstest_exe" value="C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\mstest.exe" />

<!-- custom scripts -->
<script language="C#" prefix="RuWi">
<references>
<include name="System.Xml.dll" />
<include name="System.dll" />
</references>
<imports>
<import namespace="System.Text" />
</imports>
<code>
<![CDATA[
[Function("UpdateVersionFile")]
public static bool UpdateVersionFile(string inputFile, string newVersion, bool debugMode)
{
bool ok = true;
try
{
System.IO.StreamReader versionFile = new System.IO.StreamReader(inputFile, System.Text.Encoding.ASCII);
string line = "";
System.Text.StringBuilder result = new StringBuilder();
string searchPatternVersion = @"(\d+\.\d+\.\d+\.\d+)";
string searchPatternAssemblyProduct = string.Format(@"AssemblyProduct\({0}(.*?)\{0}", "\"");
string replacePatternAssemblyProduct = string.Format(@"AssemblyProduct({0}(Debug)${1}1{2}{0}", "\"", "{", "}");

while (!versionFile.EndOfStream)
{
line = versionFile.ReadLine();

if (System.Text.RegularExpressions.Regex.IsMatch(line, searchPatternVersion) && (line.Contains("AssemblyFileVersion")))
{
line = System.Text.RegularExpressions.Regex.Replace(line, searchPatternVersion, newVersion);
}

if (debugMode && System.Text.RegularExpressions.Regex.IsMatch(line, searchPatternAssemblyProduct))
{
line = System.Text.RegularExpressions.Regex.Replace(line, searchPatternAssemblyProduct, replacePatternAssemblyProduct);
}

result.AppendLine(line);
}

versionFile.Close();

System.IO.StreamWriter updatedVersionfile = new System.IO.StreamWriter(inputFile);
updatedVersionfile.Write(result.ToString());
updatedVersionfile.Close();
}
catch (Exception ex)
{
ok = false;
Console.WriteLine(ex.ToString());
}
return ok;
}
]]>
</code>
</script>

<target name="help" >
<echo message="Removed for keeping the file shorter." />
</target>

<target name="clean" description="deletes all created files">
<delete >
<fileset>
<patternset >
<include name="**/bin/**" />
<include name="**/obj/**" />
<include name="Coverage*.xml" />
<include name="*.zip" />
<include name="MStest_Results.xml" />
<include name="simian.xml" />
</patternset>
</fileset>
</delete>
</target>

<target name="adjustversion" description="Adjusts the version in the version.info file">
<if test="${not file::exists(versionInfofile)}">
<fail message="file: ${versionInfofile} which must contains the version info was NOT found" />
</if>

<echo message="Setting version to ${CCNetLabel}" />

<property name="debugMode" value = "False" />
<property name="debugMode" value = "True" if="${configuration=='Debug'}" />
<if test="${not RuWi::UpdateVersionFile(versionInfofile,CCNetLabel,debugMode)}">
<fail message="updating file: ${versionInfofile} which must contains the version info failed" />
</if>
</target>

<target name="compile" description="compiles the solution in the wanted configuration" depends="adjustversion">
<msbuild project="${solution}" >
<arg value="/p:Configuration=${configuration}" />
<arg value="/p:CCNetListenerFile=${CCNetListenerFile}" />
<arg value="/v:${msbuildverbose}" />
<arg value="/l:${msbuildlogger}" />
</msbuild>
</target>

<target name="test" description="runs the tests" depends="deploy.services">
<if test="${string::get-length(mstest_metadatafile)>0}" >
<exec program="${mstest_exe}">
<arg value="/testmetadata:${mstest_metadatafile}" />
<arg value="/resultsfile:MStest_Results.xml" />
<arg value="/testlist:UnitTests" />
<arg value="/testlist:IntegrationTests" if="${CCNetBuildCondition=='ForceBuild'}" />
</exec>
</if>
</target>

<target name = "cover" description="runs the tests with coverage" >
<if test="${string::get-length(mstest_metadatafile)>0}" >
<!--
company rule : code coverage settings must be set via this file
with the following NamingScheme : baseName="cover_me" appendTimeStamp="false" useDefault="false"
-->
<if test="${file::exists('CodeCoverage.testsettings')}">

<exec program="${mstest_exe}" failonerror="false" resultproperty="testresult.temp" >
<arg value="/testmetadata:${mstest_metadatafile}" />
<arg value="/resultsfile:MStest_Results.xml" />
<arg value="/testsettings:CodeCoverage.testsettings" />
<arg value="/testlist:UnitTests" />
<arg value="/testlist:IntegrationTests" if="${CCNetBuildCondition=='ForceBuild'}" />
</exec>

<property name="TestsOK" value="false" unless="${int::parse(testresult.temp)==0}"/>

<property name="DataCoverageFilePath" value="${RuWi::FindFile('cover_me','data.coverage')}" />
<property name="TurnCoverageFileIntoXml_exe" value="C:\Tools\TurnCoverageFileIntoXml\TurnCoverageFileIntoXml.exe" />

<fail message="No data.coverage found in cover_me folder" unless="${string::get-length(DataCoverageFilePath)>0}" />

<echo message="DataCoverageFilePath : ${DataCoverageFilePath}" />

<exec program="${TurnCoverageFileIntoXml_exe}" >
<arg value="${DataCoverageFilePath}" />
<arg value="cover_me\Out" />
<arg value="NCoverExplorer.xml" />
</exec>

<fail message="Failures reported in unit tests." unless="${TestsOK}" />
</if>
</if>

</target>

<target name="simian" description="find duplicate code" >
<exec program="${Simian_exe}" failonerror="false">
<arg value="-includes=**/*.cs" />
<arg value="-excludes=**/*Designer.*" />
<arg value="-excludes=**/*Generated.*" />
<arg value="-excludes=**/*Reference.*" />
<arg value="-excludes=**/obj/*" />
<arg value="-threshold=10" />
<arg value="-formatter=xml:simian.xml" />
</exec>
</target>

<target name="deploy.services" description="deploys all service (web/wcf)" /> <!-- company specific, just copies files to the iis folder -->
<target name="make_package" description="makes install packages" /> <!-- company specific, creates install packages and zips them -->
<target name="makehelp" description="makes install packages" /> <!-- company specific, makes user help with custom tool -->

</project>


Source code for Ms-Test binary2Xml

Imports Microsoft.VisualStudio.CodeCoverage

Module Module1

Sub Main()
Dim Arguments As String()
Dim obc = Console.BackgroundColor
Dim returnValue As Integer = 0

Try
Arguments = Environment.GetCommandLineArgs

If Arguments.Length <> 4 Then

Console.BackgroundColor = ConsoleColor.Blue
Console.WriteLine("Usage : {0} DataCoverageFilePath CoveredFilesPath ResultXmlFilePath", Arguments(0))
Console.BackgroundColor = ConsoleColor.DarkGreen
Console.WriteLine(" {0} In\LTREMRUBEN\data.coverage Out d:\codecover.xml", Arguments(0))
Console.BackgroundColor = obc
returnValue = 1
Exit Try
End If

Dim DataCoverageFilePath As String = Arguments(1)
Dim CoveredFilesPath As String = Arguments(2)
Dim ResultXmlFilePath As String = Arguments(3)

CoverageInfoManager.ExePath = CoveredFilesPath
CoverageInfoManager.SymPath = CoveredFilesPath

Console.WriteLine("converting {0}", DataCoverageFilePath)
Dim coverage = CoverageInfoManager.CreateInfoFromFile(DataCoverageFilePath)

Dim CoverResult = coverage.BuildDataSet(Nothing)

Dim CoverResultStream As New IO.MemoryStream
CoverResult.WriteXml(CoverResultStream)

Console.WriteLine("Initial Size in bytes : {0}", CoverResultStream.Length)
CoverResultStream.Position = 0


Console.WriteLine("Cleaning up xml info ...")
Dim CoverResultXmlDoc As New Xml.XmlDocument()
CoverResultXmlDoc.Load(CoverResultStream)

Dim LineInfos = CoverResultXmlDoc.SelectNodes("//Lines")

For Each lineInfo As Xml.XmlNode In LineInfos
lineInfo.RemoveAll()
Next

Dim SourceFileNameInfos = CoverResultXmlDoc.SelectNodes("//SourceFileNames")
For Each SourceFileNameInfo As Xml.XmlNode In SourceFileNameInfos
SourceFileNameInfo.RemoveAll()
Next

CoverResultXmlDoc.PreserveWhitespace = False
CoverResultXmlDoc.Normalize()
CoverResultXmlDoc.Save(ResultXmlFilePath)

Console.WriteLine("Compressed Size in bytes : {0}", New IO.FileInfo(ResultXmlFilePath).Length)

Console.WriteLine("Done.")

Catch ex As Exception
Console.WriteLine(ex.ToString)
End Try
End Sub

End Module