|
|
2/26/2009 Part 1 talked about creating work items from unit test results. Since we operate in a TFS work item environment we needed work items to associate our changesets too when fixing unit test failures. This post is going to cover what we do when we have an orphaned, or passing, work item in TFS.
If we query TFS for open, unit test failure work items and don’t have unit test failures in the given nunit-results.xml file, we know those unit tests are now passing. We could automatically close these work items out, but that would negate the purpose for having work items in the first place. We want to know what’s happening in our system without having to work too hard to do so. So rather close the bug out, and prevent developer interaction altogether, we’ll drop the severity of the bug, annotate the “PASSING” status of the unit test and attach pertinent changeset candidates. We’ll only be able to tell the last failure date and the current, or first passing, date. So we can grab the changesets that have happened in our code branch during that time frame.
/// <summary> /// Adds a note to work items in the store for the lack of the failure in the unit test reults /// We don't want to close out the bug automatically as this would supress developer interaction with the defect /// </summary> public void ProcessResolutions() { foreach (WorkItem failureWorkItem in UnitTestWorkItems) { WorkItem foundWorkItem = (from existingWorkItem in _failureWorkItems where existingWorkItem.Title == failureWorkItem.Title select existingWorkItem).FirstOrDefault(); if (foundWorkItem == null && GeneratePassingNotes) { failureWorkItem.Open(); ///attach changesets from first prior failure to now ///this helps us determine what might have caused this to change state AttachChangesets(failureWorkItem); failureWorkItem.History = "PASSED"; failureWorkItem.Fields["Conchango.TeamSystem.Scrum.TestingImpact"].Value = "5 - Low"; failureWorkItem.Save(); } } }
/// <summary> /// Gets all the changesets between the last failed test and the first passing test. /// These are attached to the work item to assist in resolution. /// </summary> /// <param name="passingWorkItem"></param> public void AttachChangesets(WorkItem passingWorkItem) { DateTime passingDate = DateTime.Now; DateTime lastFailDate = passingWorkItem.CreatedDate; foreach (Revision revision in passingWorkItem.Revisions) { if (revision.Fields["System.History"].Value.ToString() == "PASSED") { passingDate = Convert.ToDateTime(revision.Fields["Revised Date"].OriginalValue.ToString()); lastFailDate = Convert.ToDateTime(revision.Fields["Conchango.TeamSystem.Scrum.HistoryDate"].OriginalValue.ToString()); break; } } string folderPath = string.Format(@"$/{0}/{1}/", ProjectName, BranchName); VersionSpec startSpec = VersionSpec.ParseSingleSpec("D" + lastFailDate, string.Empty); VersionSpec endSpec = VersionSpec.ParseSingleSpec("D" + passingDate, string.Empty); LogMessage(string.Format(@"Finding changesets between {0} and {1} for WorkItem {2}", lastFailDate.ToString(), passingDate.ToString(), passingWorkItem.Id)); IEnumerable changesets = _versionControlServer.QueryHistory(folderPath, VersionSpec.Latest, 0, RecursionType.Full, null, startSpec, endSpec, int.MaxValue, true, true, true); RegisteredLinkTypeCollection linkTypes = _store.RegisteredLinkTypes; RegisteredLinkType changesetLinkType = linkTypes["Fixed in Changeset"]; ExternalLink changesetLink; foreach (Changeset changeset in changesets) { changesetLink = new ExternalLink(changesetLinkType, changeset.ArtifactUri.ToString()); try { passingWorkItem.Links.Add(changesetLink); } catch (ValidationException tfsValidationException) { if (tfsValidationException.Message.Contains("This work item already has a duplicate link")) { LogMessage(MessageImportance.Normal, "Attaching the changesets failed. " + tfsValidationException.Message); } else { throw tfsValidationException; } } } }
Find the whole class file here. Working in the Microsoft stack in today's world typically means working in Team Foundation Server. I'm fine with it. Is TFS perfect? No, but it's pretty powerful.
In a mix between micro-management and a development spike, I decided to take our unit test results a step further and generate TFS work items for failing and ignored unit tests. This will serve as a shell to help auditing some unit test metrics and give us a work item to assign the resolution too. I think ultimately this will be a fluent process, but it's still a process. Before all we had was a global task for unit test failures. Management will certainly appreciate the granularity we're now delivering.
Capitalizing on the Microsoft.Build.Utilities.Task class, one can make a custom task to be executed by MSBUILD. This is easy enough to do, just bring in the namespace/class to the custom class and override the Execute() method. Then reference the custom assembly in the proj file that drives your MSBUILD execution by inserting a "UsingTask" node, like so:
< UsingTask TaskName="Custom.TFS.Utilities.UnitTestWorkItemGenerator" AssemblyFile="$(MSBuildExtensionsPath)\NCMCustomTasks\NCM.Custom.TFS.Utilities.dll" />
The main things involved in the WorkItemGenerator is creating a list of WorkItems and comparing that list to the TFS store to either create new work items or append a note to existing work items. As with the nunit-summary.exe tool, we're going to operate on the nunit-results.xml file.
The first useful operation in this utility is the generation of the TFS work items for every failure in the nunit-results.xml file. We use the Conchango SCRUM templates for Team System and have created some of our own fields. To get those specific field names I had to open up the work item templates defined for our project.
/// <summary> /// Iterates through the failure nodes in the given nunit-results.xml file and return a list of /// work items. The last line in the failure message stack trace is used to find the corresponding /// fixture class file in version control and link it to the work item /// </summary> public List<WorkItem> GenerateFailureWorkItems() { XmlNodeList failureNodes = _nunitResultsDocument.SelectNodes(@"//test-case[failure]"); List<WorkItem> failureWorkItems = new List<WorkItem>(); if (failureNodes != null) { foreach (XmlNode failureNode in failureNodes) { ///need to strip out the namespace from the method string testMethodName = failureNode.Attributes["name"].Value; testMethodName = testMethodName.Substring(testMethodName.LastIndexOf(".") + 1); //up one to the <results> node //up one more to the <test-suite> node string title = string.Format("Unit Test Failure: {0}.{1}", failureNode.ParentNode.ParentNode.Attributes["name"].Value, testMethodName); string stackTrace = failureNode.FirstChild.ChildNodes[1].InnerText; StringBuilder descriptionBuilder = new StringBuilder(); descriptionBuilder.AppendLine("Test Method: " + testMethodName); descriptionBuilder.AppendLine("Error Message: " + failureNode.FirstChild.ChildNodes[0].InnerText); descriptionBuilder.AppendLine("Stack Trace: " + stackTrace); WorkItemType wiType = TFSProject.WorkItemTypes[BUG_WORK_ITEM_NAME]; ///get the current sprint item WorkItem currentSprintItem = RetrieveSprint()[0]; WorkItem newWorkItem = new WorkItem(wiType) { Title = title, Description = descriptionBuilder.ToString(), IterationPath = currentSprintItem.IterationPath, AreaPath = AreaPath }; newWorkItem.Fields["Conchango.TeamSystem.Scrum.Team"].Value = "Developer"; newWorkItem.Fields["Conchango.TeamSystem.Scrum.TestingImpact"].Value = "3 - High"; newWorkItem.Fields["Conchango.TeamSystem.Scrum.DateDiscovered"].Value = DateTime.Now; newWorkItem.Fields["Custom.Bug.Acceptance"].Value = "Accepted"; newWorkItem.Fields["Custom.URL"].Value = "N/A"; newWorkItem.Fields["Custom.DefectCategory"].Value = "Unit Test Failure"; ///get the last line of the description stackTrace = stackTrace.Substring(stackTrace.LastIndexOf(@"x:\")); ///insert the TFS relative path string itemPath = stackTrace.Replace(string.Format(@"x:\Builds\Custom\Source\{0}\Sources", BuildDefinitionId) , VersionControlPathRoot); itemPath = itemPath.Substring(0, itemPath.IndexOf(":")); itemPath = itemPath.Replace(@"\", @"/"); ///try to find the referenced source control asset LogMessage("Version Control ItemPath: " + itemPath); Item versionControlItem = _versionControlServer.GetItem(itemPath, VersionSpec.Latest); if (versionControlItem != null) { RegisteredLinkTypeCollection linkTypes = _store.RegisteredLinkTypes; RegisteredLinkType versionedItemLinkType = linkTypes["Source Code File"]; ExternalLink itemLink = new ExternalLink(versionedItemLinkType, versionControlItem.ArtifactUri.ToString()); newWorkItem.Links.Add(itemLink); } failureWorkItems.Add(newWorkItem); } } return failureWorkItems; }
public WorkItemCollection RetrieveSprint() { Hashtable parameters = BuildQueryParameters(); string query = BuildSprintWorkItemQuery(); return _store.Query(query, parameters); } private Hashtable BuildQueryParameters() { Hashtable parameters = new Hashtable(); parameters.Add("project", ProjectName); return parameters; } private string BuildSprintWorkItemQuery() { return @"SELECT [System.Id], [System.WorkItemType], [System.AssignedTo], [System.CreatedBy], [Microsoft.VSTS.Common.Priority], [System.Title], [Conchango.TeamSystem.Scrum.SprintStart],[Conchango.TeamSystem.Scrum.SprintEnd] FROM WorkItems WHERE [System.TeamProject] = @project AND [System.WorkItemType] = 'Sprint' AND [Conchango.TeamSystem.Scrum.SprintStart] <= @Today AND [Conchango.TeamSystem.Scrum.SprintEnd] >= @Today AND [System.State] <> 'Deleted'"; }
This same pattern is followed to generate the Ignored unit test work items. Ignored tests are basically failing tests we're not ready to fix yet. Therefore they are treated much the same way and get work items created that will follow us from sprint to sprint.
Once we have all our work items we go to TFS and see if this is a new work item or not. If so, we just save the created work item as-is, otherwise we'll run some updates against the existing work item.
/// <summary> /// Adds a note to existing work items or Saves new failures to the store /// </summary> public void ProcessFailures() { foreach (WorkItem failureWorkItem in _failureWorkItems) { WorkItem existingFailureWorkItem = RetrieveUnitTestWorkItem(failureWorkItem); if (existingFailureWorkItem != null) { if (GenerateFailureNotes) { existingFailureWorkItem.Open(); existingFailureWorkItem.IterationPath = failureWorkItem.IterationPath; if (existingFailureWorkItem.Description != failureWorkItem.Description) { existingFailureWorkItem.Description = failureWorkItem.Description; } existingFailureWorkItem.History = "FAILED"; ///this was mainly to support backward compatability ///if there are work items floating around out there that ///didn't get version control links we'll add them foreach (ExternalLink itemLink in failureWorkItem.Links) { try { existingFailureWorkItem.Links.Add(itemLink); } catch (ValidationException tfsValidationException) { if (tfsValidationException.Message.Contains("This work item already has a duplicate link.")) { ///log & gulp LogMessage(MessageImportance.Normal, "Saving the linked version control item failed. " + tfsValidationException.Message); } } } existingFailureWorkItem.Save(); } _failureWorkItemIds.Add(existingFailureWorkItem.Id); } else { if (GenerateNewFailures) { failureWorkItem.Save(); } _failureWorkItemIds.Add(failureWorkItem.Id); } } }
The next post will go over what happens with the passing unit tests. 12/22/2008 So my current project has a pretty cohesive TFS 08 build process. We have automated CI builds, parameter driven deployment drops, and what I've been working on most recently; daily and nightly unit test suite exection. I haven't looked at our coverage in a while, but as of this writing, we have over 1200 unit tests. When our build ninja master, Shane Sawyer, set up these unit test builds, he made a custom task to email the results to the team. This was very useful and not as annoying as a generic build results email (AKA trash filter spam), so it was welcomed by the team. It's been our main tool for broadcasting and working unit test results. This process had been green for a while and some downtime towards the end of the year opened up some refactoring opportunities, so I jumped in.
The NUnit call in the build .proj file outputs results to a handy xml file. A call to the nunit-summary.exe tool transformed the raw xml results to a more usable outline of all the ignored and failed tests with a nice summary of total tests ran, total failed, total ignored, and the time it all took at the top. This dropped to a .txt file on a server location and the email task in the build process attached it to the email. While very useful, it was a bit of a chore to browse through all the failures to determine where the heavy bleeding was and begin work accordingly. Thankfully, Charlie Poole is a genius and gave us the means to pass in a custom xslt file to transform the NUnit results more ways than Starscream. So here's our new nunit-summary.exe call and my custom xslt file.
<msbuild .proj call>
<EXEC IgnoreExitCode="false" WorkingDirectory="$(SolutionRoot)\Binaries" Command='CMD /C nunit-summary.exe "$(DropLocation)\$(BuildNumber)\NUnit\NUnit-Results.xml" -out="$(DropLocation)\$(BuildNumber)\NUnit\NUnit-Summary.htm" -xsl="$(ResultsXSLT)"' /> <the xslt>
<? xml version="1.0" encoding="UTF-8" ?>< xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">< xsl:output method='html'/>< xsl:template match="/">< html>< xsl:apply-templates/></ html></ xsl:template>< xsl:template match="test-results">< head>< script type="text/javascript">function ShowHideDetails(contentControlId) { var contentControl = document.getElementById(contentControlId); if(contentControl) { if(contentControl.style.display == 'none') { contentControl.style.display = ''; } else { contentControl.style.display = 'none'; } } } </ script></ head>< body>< div id="uxHeader" style="font-weight:bold;">Tests run: <xsl:value-of select="@total"/>, Failures: <xsl:value-of select="@failures"/>, Not run: <xsl:value-of select="@not-run"/>, Time: <xsl:value-of select="test-suite/@time"/> seconds</ div>< xsl:if test="//test-case[failure]">< div style="font-weight:bold;margin-top:10px;border-top:1px solid black;font-size:14pt;">Failures </ div>< table>< xsl:for-each select="//test-suite">< xsl:if test="results/test-case[@success = 'False']">< tr>< td>< xsl:value-of select="@name" />:</ td>< td style="width:100%;">< a href="#" >< xsl:attribute name="onclick">ShowHideDetails('uxContent <xsl:value-of select="@name"/>');</ xsl:attribute>< xsl:value-of select="count(results/test-case[@success = 'False'])" /></ a></ td></ tr>< tr style="display:none;" >< xsl:attribute name="id">uxContent<xsl:value-of select="@name"/></xsl:attribute>< td colspan="2" >< div style="margin-left:10px;border:1px solid black;width:900px;height:300px;overflow:auto;background-color:silver;">< xsl:call-template name="failureTemplate" ></xsl:call-template></ div></ td></ tr></ xsl:if></ xsl:for-each></ table></ xsl:if>< xsl:if test="//test-case[@executed = 'False']">< div style="font-weight:bold;margin-top:10px;border-top:1px solid black;font-size:14pt;">Ignored Tests </ div>< xsl:for-each select="//test-suite">< xsl:if test="results/test-case[@executed = 'False']">< div style="margin-top:5px;font-weight:bold;"><xsl:value-of select="@name" /></div>< div style="margin-left:10px;">< xsl:call-template name="ignoreTemplate"></xsl:call-template></ div></ xsl:if></ xsl:for-each> </ xsl:if></ body></ xsl:template>< xsl:template match="results/test-case[failure]" name="failureTemplate">< xsl:for-each select="results/test-case[@success = 'False']">< pre>< span style="font-weight:bold;font-size:12pt;"><xsl:value-of select="position()"/>)</span><xsl:value-of select="@name"/>:<br/>< xsl:value-of select="child::node()/message"/>< xsl:if test="failure"><xsl:value-of select="failure/stack-trace"/></xsl:if></ pre></ xsl:for-each></ xsl:template>< xsl:template match="results/test-case[reason]" name="ignoreTemplate">< xsl:for-each select="results/test-case[@executed = 'False']">< span style="font-weight:bold;font-size:12pt;">< xsl:value-of select="position()"/>)</ span>< xsl:value-of select="@name"/>< div style="font-size:10pt;margin-left:25px;">Reason:< xsl:choose><xsl:when test="string-length(child::node()/message)=0"> [not defined]</xsl:when>< xsl:otherwise> "<xsl:value-of select="child::node()/message"/>"</xsl:otherwise></ xsl:choose></ div></ xsl:for-each></ xsl:template></ xsl:stylesheet>
So there you go. I don't have the time to create a cohesive, IP free, unit test suite to demonstrate this, but if you're interested in this so far, you should have your own damn unit tests to operate on this. And remember kids, write your tests before you write your code, chocolate rain! 2/23/2008 I had a bit of a scare as I found this article ~3 hrs after I'd left my PC at home to bake on the SP1 install the other day. However, my fears have not manifested and everything appears to be running swimmingly...for now. My Portal game now finally runs, with sound. wo0t!!1! I had it running at one point on my Vista 64 Ultimate installation with nothing but video. Everyone kept telling me that the sound made the game, and it certainly does. I tried reinstalling some different drivers and everything went to hell with it. I couldn't get any of the Orange Box to run then. I was getting sporatic blinky black screens with an eventual crash leaving me looking at my desktop again, provided I didn't have to task/process kill it. After the SP1 install finshsed I was hesitant to boot Portal for a few days. Today was the day and I was rewarded. The game clips a little on the area sweeping; hyper sensitive mouse movements but that's likely due to my hardware shortcomings. I could use a new dual core and graphics card (couldn't we all). The 4gb of RAM is handling all operations pretty well though. Hopefully SP1 is the answer and my installation stays solid. Probably time for a defrag and backup though...  1/20/2008 Well I finally got off my lazy ass and installed the hell out of Expression Blend 2 and VStudio 08 TS on my home Vista 64 box. I mean that too. It was a hard few hours of figuring what needs to be installed to have a Silverlight designer/developer environment. I thought the Blend 1 I got at Mix07 did it, apparently I missed that session or didn't pay attention enough in class. S- for me on that one.
Anywho, I've got the environment up now and have my rotating and moving text in a timeline. wo0t!!1 Now I need to figure out how to do something usefull. Like a proof of concept to have a drag and drop interface for media clips. I ordered one of the few Silverlight 1.0 books in print at the moment so hopefully that helps, because I'm uber-green to this. I'm not scared of the Javascript, but I'm a little worried that it might just be worth tooling around in a Silverlight 1.x sandbox environment and waiting for the C# object model in 2.0 before I get serious about production value. 11/20/2007 Like any massive release like this, it needed to be quarantined. I've learned that lesson the hard way, thank you Vista 64 and
Office 2007. So I fired up a new VPC07 XP32 instance and went crazy.
After getting over my compulsive clicking past the 'Default', 'Everything', and 'Custom' install option to view the install bits, I was
finally on my way. The install is exactly like 2005, only with different stuff being installed (obviously) and different Visual Studio Model talent. Altough there's only three models. Come on I thought this was an upgrade. I call them the soccer team [1]. The whole concept of the rotating model banner is insulting. First they captivate me by providing useful information, like the list of install items and the status bar of the currently being installed item. Very useful, thank you. But they give the major
real estate to their new feature sales descriptions and these stupifying models. I don't want MTV driving my goals as a Microsoft developer. It pissed me off with 2005 and pisses me off now. Grr.
For all the shallow ape candy it displayed on setup, it did finish successfully and in relatively little time, given the ~856 mb of RAM I gave my VPC. Now I'm off to enjoy it's uses.
[1] - The mom
The coach?
10/16/2007 Holy cow this game took me forever. Between Halo 3 coming out [more on that later], the Rockies making the World Series and work, my Bioshock time wasn't very abundant. My wife wanted some time in there too.
After I managed to kill my first Big Daddy, I was on my way. My nerves never really calmed down unless it was light outside. The audio on that game was just nails. The clarity, volume, direction and reflection were covered like no other game I've played. You can hear the baddies through thin walls, around corners (so the sound comes from the opening and responds with higher and lower volume based on your distance), and hear reflection off large tile or stone rooms and hear deadened sound and voices in more confined and sound dampened areas, like wood.
The plot twisted and turned like Twin Peaks episode and you don't stop when you think you should. Overall a very engrossing story that legitimizes it's "magics" much better than most games. Though the astute gamer will still be left wondering why some things are the way they are.
All in all, I'd say it's in my top 3 of all time. 9/17/2007 So everyone loves Bioshock...me too. It's beautiful visually, but nothing too industry leading there. The sound breaks the mold though. In my 6.1 surround sound I can hear, as soon as I move, where a given sound is coming from. I've played nothing with that kind of volume and directional audio response. The story is engrossing like Half-Life, no surprise given the Havoc license, and Myst. I'm not nearly finshed with it yet. I just started it yesterday, and played a little in to the night, but I don't have the nerves of steel that I used to in my Silent Hill days. This game has me jumping at anything. It permeated my dreamscape well after I put the controller down. I'm well past the Bungie formula of 30 seconds of fun, repeat...but I'll deliver some more on the game as I go none-the-less. 8/21/2007 Here's my WatiN based smoke test to open the default page of the web app.
The basic structure is that my base class exposes a HostURL string that refers to a config value and IE object that has its' various timeout properties set from config values
<basetest.cs>
using System;using System.Collections.Generic;using System.Text;using WatiN.Core;namespace SmokeTests{ public class BaseTest { public string HostUrl { get { return System.Configuration.ConfigurationSettings.AppSettings["HostUrl"].ToString(); } } public IE MyIE(string Url) { int timeout = int.Parse(System.Configuration.ConfigurationSettings.AppSettings["IETimeout"].ToString()); IE.Settings.AttachToIETimeOut = timeout; IE.Settings.WaitForCompleteTimeOut = timeout; IE.Settings.WaitUntilExistsTimeOut = timeout; return new IE(Url); } } }
<LoadDefaultPage.cs>
using System;using System.Text;using System.Collections.Generic;using Microsoft.VisualStudio.TestTools.UnitTesting;using WatiN.Core;using System.IO;namespace SmokeTests{ [TestClass] public class LoadDefaultPage :BaseTest { [TestMethod] public void LoadPage() { // create a new Internet Explorer instance pointing to the ASP.NET Development Server using (IE ie = MyIE(String.Format("http://{0}/Project1/default.aspx", HostUrl))) { // parse the response in order to fail the test or not Assert.AreEqual( true, ie.Frames[0].ContainsText("Customer Name")); } } } }
that's it for now. provide feedback if you will So with the "h4ck" of running the CruiseControl service on the build server as an existing AD authenticated user, we can now run our automated web app unit test. I say "h4ck" not so much because of the logic of using an existing AD user, but because it's somewhat of a corporate h4ck. The user is one of the logins one of us was assigned from operations for general network node usage. It's easier for us to control this way in that we've got someone on the team that can handle the user. Is this the premium solution? Hell no, but it suits the purpose and so far has been reliable and until it ceases to be so, we'll maintain it. Works for NASA, right?
Some code goodies...
We used msbuild to build the existing sln files as is, no msbuild sepecifc proj file is used. We just use the queue and queuePriority attributes of the project node then define a forcebuild publisher for the next project to be built in the cycle.
<ccnet.config>
<? xml version="1.0" encoding="utf-8" ?><!-- somewhat of an xml hack to use a global constant in this ccnet.config file--><! DOCTYPE cruisecontrol[<!ENTITY BaseDirectory "F:\FolderName"><!ENTITY BaseDrive "F:">]> < cruisecontrol xmlns="http://www.sf.net/projects/ccnet">< project name="Project1" queue="DemoProject" queuePriority="1">< sourcecontrol type="vss" autoGetSource="true" applyLabel="false">< executable>&BaseDrive;\Program Files\Microsoft Visual SourceSafe\ss.exe</executable>< project>$/Project1</project>< username>Username</username>< password></password>< ssdir>\\server\folder</ssdir>< workingDirectory>&BaseDirectory;\Project1</workingDirectory>< cleanCopy>true</cleanCopy></ sourcecontrol>< triggers>< intervalTrigger seconds="600" /></ triggers>< tasks><!-- Clean the deployment folders-->< nant>< executable>C:\nant\bin\nant.exe</executable>< baseDirectory>&BaseDirectory;\ContinuousIntegration</baseDirectory>< buildFile>master.build</buildFile>< targetList>< target>clean-deploy</target></ targetList></ nant>< msbuild>< executable>C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\MSBuild.exe</executable>< workingDirectory>&BaseDirectory;\Project1</workingDirectory>< projectFile>Project1.sln</projectFile>< buildArgs>/noconsolelogger /p:Configuration=Debug /v:diag</buildArgs>< targets>Build</targets>< timeout>60</timeout>< logger>ThoughtWorks.CruiseControl.MsBuild.XmlLogger,ThoughtWorks.CruiseControl.MsBuild.dll</logger></ msbuild> </ tasks> < publishers>< forcebuild>< project>Project2</project> </ forcebuild>< xmllogger logDir="F:\program files\cruisecontrol.net\server\Project4\Artifacts\buildlogs"/></ publishers> </ project>< project name="Project2" queue="DemoProject" queuePriority="4">< sourcecontrol type="vss" autoGetSource="true" applyLabel="false">< executable>&BaseDrive;\Program Files\Microsoft Visual SourceSafe\ss.exe</executable>< project>$/Project2</project>< username>Username</username>< password></password>< ssdir>\\server\folder</ssdir>< workingDirectory>&BaseDirectory;\Project2</workingDirectory>< cleanCopy>true</cleanCopy></ sourcecontrol>< triggers>< intervalTrigger seconds="600" /></ triggers>< tasks>< nant>< executable>C:\nant\bin\nant.exe</executable>< baseDirectory>&BaseDirectory;\ContinuousIntegration</baseDirectory>< buildFile>master.build</buildFile>< targetList>< target>pokeXml-smokeTestInit</target></ targetList></ nant>< msbuild>< executable>C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\MSBuild.exe</executable>< workingDirectory>&BaseDirectory;\Project4</workingDirectory>< projectFile>Project4.sln</projectFile>< buildArgs>/noconsolelogger /p:Configuration=Debug /v:diag</buildArgs>< targets>Build</targets>< timeout>60</timeout>< logger>ThoughtWorks.CruiseControl.MsBuild.XmlLogger,ThoughtWorks.CruiseControl.MsBuild.dll</logger></ msbuild>< exec>< executable>C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\mstest.exe</executable>< baseDirectory>&BaseDirectory;\Project4\SmokeTests</baseDirectory>< buildArgs>/testcontainer:"&BaseDirectory;\Project4\SmokeTests\SmokeTests\bin\debug\SmokeTests.dll" /runconfig:"&BaseDirectory;\Project4\SmokeTests\localtestrun.Testrunconfig" /resultsfile:"&BaseDirectory;\Project4\SmokeTests\testResults.trx"</buildArgs>< buildTimeoutSeconds>300</buildTimeoutSeconds></ exec>< nant>< executable>C:\nant\bin\nant.exe</executable>< baseDirectory>&BaseDirectory;\ContinuousIntegration</baseDirectory>< buildFile>master.build</buildFile>< targetList>< target>pokeXml-smokeTestFinalize</target></ targetList></ nant></ tasks>< publishers>< merge>< files>< file>&BaseDirectory;\Project2\SmokeTests\testResults.trx</file></ files></ merge>< xmllogger /></ publishers></ project> </ cruisecontrol>
<master.build>
<? xml version="1.0"?>< project name="NAnt Examples" default="build">< property name="pokeConfig" value="1"/>< property name="rootUrl" value="server:80"/>< property name="rootDirectory" value="F:\Folder"/>< property name="sourcesafe.Project1.path" value="$/Project1" />< property name="sourcesafe.dbpath" value="\\server\folder" />< property name="project.source.dir" value="${rootDirectory}\Project1" />< property name="Project1.dir" value="${rootDirectory}\Project2" />< property name="ClientAPI.dir" value="${rootDirectory}\Project3" />< property name="Project2.dir" value="${rootDirectory}\Project4" />< property name="sourcesafe.user" value="Username" />< target name="clean-deploy" description="Cleans up deploy files">< echo message="Deleting Deployment Files" />< delete failonerror="false" >< fileset basedir="${Project2.dir}" >< include name="**" /></ fileset></ delete>< mkdir dir="${Project2.dir}" failonerror="false" /><!-- have to hack this in as the sourcesafe task in ccnet.config won't cover this in the procedural execution -->< exec program = "SS" commandline="GET ${sourcesafe.Project1.path} -GL${Project2.dir} -Y${sourcesafe.user} -R -I-Y" failonerror="false" />< delete failonerror="false" >< fileset basedir="${Project1.dir}" >< include name="**" /></ fileset></ delete>< mkdir dir="${Project1.dir}" failonerror="false" />< delete failonerror="false" >< fileset basedir="${ClientAPI.dir}" >< include name="**" /></ fileset></ delete>< mkdir dir="${ClientAPI.dir}" failonerror="false" />< delete failonerror="false" >< fileset basedir="${project.source.dir}" >< include name="**" /></ fileset></ delete>< mkdir dir="${project.source.dir}" failonerror="false" /></ target> < target name="dev-build">< call target="pokeXml-build" /></ target> < target name="pokeXml-build" description="Pokes the web.config xml to use build server addresses">< if test="${pokeConfig == '1'}">< xmlpoke file="${project.source.dir}\web.config" xpath="/configuration/appSettings/add[@key = 'Domain.Namespace.WebService']/@value" value="http://${rootUrl}/WebService.asmx" /></ if>< call target="pokeXml-smokeTestInit"/></ target> < target name="pokeXml-smokeTestInit" description="Pokes the web.config xml to turn BypassAuthentication on for the smoke test">< xmlpoke file="${project.source.dir}\web.config" xpath="/configuration/appSettings/add[@key = 'AuthorizationOverride']/@value" value="true" />< call target="delete-testResults"/></ target> < target name="pokeXml-smokeTestFinalize" description="Pokes the web.config xml to turn BypassAuthentication off for normal use">< xmlpoke file="${project.source.dir}\web.config" xpath="/configuration/appSettings/add[@key = 'AuthorizationOverride']/@value" value="false" /></ target>< target name="delete-testResults" description="Deletes test results file to make room for new one">< echo message="Deleting Test Results File" />< delete failonerror="false" file="${project.source.dir}\SmokeTests\testResults.trx" /> </ target></ project>
due to my blog space limitations, the next code goodies will be in a followup post 8/3/2007 I'm on my first dev lead role (yay I don't suck) for the .Net web app that the internal and external CSR's use at the various call centers for my company. One of my first initiatives was to upgrade the existing continuous integration process. They had CruiseControl running but it was a pre 1.x version and as such only ran one project and no direct MSBuild integration. So I've got it up to 1.3 and building the multiple projects related to this title by .sln file.
Also along these lines I wanted to implement some smoke testing. As fun as getting in Monday mornings to find that the Friday checkins broke the site is, I thought it better not to start the week with a headache as much as possible. My greatest dev mentor, Richard Lawrence, introduced me to pretty much all of my continous integration concepts. At the time I was working for him, he had us using WatiR executed by NUnit unit tests that tied into the build process nicely via NAnt calls in the ccnet.config file. I started to implement this same structure for my new endevour until I found some random response by Scott Guthrie on some guys blog (I couldn't tell you what it was at the moment) that suggested WatiN. Scripting IE web page execution via .Net, that can be executed from Visual Studio unit tests? I'm in! This requires only one dll reference in the unit test project and doesn't require my dev's pickup Ruby.
I've got this running, for the most part, using MSTest to execute the VStudio unit tests in ccnet.config. Now I just need to jump through some network impersonation hoops at CI build time. I'm still working at it, I'll post some code excerpts when I've got it gold.
4/29/2007 I got here late, ~21:30. So I missed registration and the beginning of the mashup. But the bar's still open! Is it everything I hoped it'd be? I dunno. Having never been to one of these things, it looks and feel pretty good. It seems like there was a good deal of time and money put into just this opening night Mashup thing. Lotsa pretty lights, computers (for me to blog and read up on the latest buzz on Mix07), open bar (raspberry cosmos ho!) and white boards and large presentation paper pads for jotting ideas on. Ideas like: bicycle air-bags, personal computers doing everything from translating dog to diagnosing medical conditions, ad nauseum. I guess I am pleasantly surprised that these kind of ad-hoc, conference generated think tanks are given a habitat to flourish in, even if the bar is open.
|