Troubleshooting Non-Reproducible Bugs in Visual Studio 2010

There are two new features in Visual Studio 2010 that help developers troubleshoot bugs that can't be easily reproduced.  One is IntelliTrace, which seems like it will be of limited usefulness to me because it can’t be done remotely, but it might be more useful for teams that have a dedicated QA staff and/or don't practice TDD.  On the other hand, I watched an episode of Hanselminutes where Scott interviews Tess Fernandez about Debugging Crash Dumps in VS2010, and that technique seems like it could be really useful.

IntelliTrace is only available in Visual Studio 2010 Ultimate.  It seems to collect the values of all in-scope variables whenever an IntelliTrace event occurs.  Those watched events are configurable (from a long, but pre-determined list), and include things like button clicks, writing to the Debug log, registry access, etc.  If you encounter some non-desirable functionality in an application while debugging (or running their test tool), you can break in the debugger, and then see what the state was whenever one of the watched events occurred.  There is also an option to basically capture the state whenever a method is called, but that entails a significant performance penalty, as one might expect.  I found an article on MSDN that says: “With IntelliTrace, you can debug applications launched from Visual Studio and log files that were created by IntelliTrace or the test tool Test and Lab Manager. You cannot use IntelliTrace with applications launched outside Visual Studio and attached using the Attach to command. IntelliTrace does not support remote debugging of applications that are running on other computers.”  That implies to me that there’s no real way to use this functionality to figure out what happened when a bug occurred on a user’s computer, unfortunately.  That article is here: http://msdn.microsoft.com/en-us/library/dd264915(VS.100).aspx .  Also note that although you can step backwards in the debugger (and step out of methods to see what the variables were in the calling method) in IntelliTrace, you can only step in increments of IntelliTrace events (which can include method calls if you configured VS that way), so it will step over big chunks of code if they don’t include watched events.

Here are some posts about how to get started using IntelliTrace: http://blogs.msdn.com/habibh/archive/2009/10/20/getting-started-with-visual-studio-2010-intellitrace-hello-intellitrace.aspx and http://blogs.msdn.com/habibh/archive/2009/10/21/the-future-of-debugging-is-here-visual-studio-2010-now-supports-stepping-back-in-the-debugger.aspx

Debugging crash dumps, however, does seem like something that can be done to troubleshoot undesirable functionality that happened on a user’s computer (or on a production server).  Apparently in Vista and Windows 7, there is functionality in Task Manager such that you can right-click on a process and choose “Create Dump File” which creates a file in “the temp directory”.  (Apparently there are separate apps you can download to generate such dump files with Windows XP, such as “DebugDiag”, but I'm not sure if that works with managed code.)  You can then open the resulting file in Visual Studio 2010 and, provided you have the source code for the application, debug it in “Mixed Mode” (meaning you can see what was happening in managed code as well as in Native code).  Although you can’t step in the debugger when you’ve loaded a dump file, you can see all the values of the variables in memory (by highlighting over them in code just like typical debugging), and using the “Parallel Stacks” window in VS2010, you can get a visualization of the code that all active threads were executing when the dump was generated, and click through to see what all the variables were set to in a given thread when the dump was generated.  It was implied in passing that this will work with applications compiled for .NET 3.5.  It seems to me this would be incredibly useful for troubleshooting a problem that can’t be reproduced.  When the problem happens, just tell the user to create a dump file, send it to development, and then see what was happening with their app at the time.  That screencast is here: http://channel9.msdn.com/posts/Glucose/Hanselminutes-on-9-Debugging-Crash-Dumps-with-Tess-Ferrandez-and-VS2010/  I'm excited about this functionality, and I'm hoping to be able to spend more time trying to debug Crash Dumps created from some of my company's WinForms applications, and seeing if it is feasible to do from Windows XP (which is installed on many of the workstations running our applications).  When and if that comes to fruition, I'll be writing another post on this topic.

Getting ActiveRecord and NHibernate Working in Medium Trust

I have spent the last couple of hours trying to figure out how to get Castle ActiveRecord working in medium trust (using GoDaddy), and I have finally succeeded (for my purposes).

Here are the steps I had to take to get this working:

 

* Re-built ActiveRecord to allow partially trusted callers, which entails the following steps: (thanks to this thread on the Castle Project forums)

1. Do an SVN Checkout on the Castle project trunk using TortoiseSVN or Subversion. The repository is here: http://svn.castleproject.org:8080/svn/castle/trunk

2. Navigate to the root directory of the Castle project on your machine and enter the following command:

SharedLibs\build\NAnt\bin\nant -t:net-3.5 -D:assembly.allow-partially-trusted-callers=true

Naturally, the build fails (running unit tests), but I was none-the-less able to get the updated DLLs I needed from the build\net-3.5\debug directory under the Castle directory.  I updated all the ActiveRecord-related DLLs (and NHibernate, etc.) that my app was using from the release version of ActiveRecord to this newly-compiled version.  It also now needs Iese.Collections.dll and NHibernate.ByteCode.Castle.dll

3. I added the following line to my ActiveRecord configuration file (appconfig.xml), which is required by newer versions of NHibernate (thanks to these instructions on NHForge):

<add key="proxyfactory.factory_class" value="NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle" />

* Enable AllowPartiallyTrustedCallers by adding the following code to AssemblyInfo.cs:

[assembly: System.Security.AllowPartiallyTrustedCallers]

 

* Place this line after your call to ActiveRecordStarter.Initialize(...): (thanks to these instructions on NHForge)
NHibernate.Cfg.Environment.UseReflectionOptimizer = false;

 

Apparently getting Lazy Loading to work in Medium Trust is even harder, but there is apparently a tool available called NHibernate Proxy Generator that will enable that to work, although it entails generating proxies at compile-time because Reflection is not allowed under Medium Trust.  Here is the tale of an individual who was apparently able to get this to work: http://blechie.com/WPierce/archive/2008/02/17/Lazy-Loading-with-nHibernate-Under-Medium-Trust.aspx  At present, I don't need lazy-loading in the application that I'm running in Medium Trust, so I haven't tried it.

 

Good luck!  Leave a comment if this worked for you, or if you have any problems.

Updated Presentation Materials: Developing REST Web Services using WCF

Here are the presentation materials that were slightly revised for the second time I grave the WCF/REST presentation (on June 3rd in the South Bay).  Sorry for the delay!

Presenting on REST/WCF in the South Bay

I am doing my Developing REST Web Services using WCF presentation again for the South Bay chapter of the Bay.NET user group.  It will be Wednesday, June 3rd at the Microsoft’s Silicon Valley conference center at 1065 La Avenida St, Mountain View, CA (Building 1 - room number is still TBD).

Developing REST Web Services using WCF - Presentation materials

As promised in the presentation I did last night for NBNUG, here is my slide deck and sample code for my REST/WCF presentation.  As I mentioned in the presentation, the sample code is purposefully not well-factored, so as not to obscure the core concepts that the code is demonstrating.  The sample code is not at all meant to convey architectural best practices for WCF-based applications, it's essentially an illustration to get folks started working with this technology.

del.icio.us Tags: ,,,

Presenting at NBNUG on 4/21: Developing REST Web Services using WCF

I am demonstrating how to create a REST-based web service using WCF at the upcoming NBNUG meeting.  Here is the abstract:

Tuesday, 4/21/2009, meeting at 7:00 PM

O'Reilly Media in Sebastopol

1003-1005 Gravenstein Highway North, Sebastopol
Tarsier Conference Room (between Building B and Building C)
(8 miles west of Santa Rosa)

The presenter will walk through a working REST-based web service (and client), implemented with WCF (Windows Communication Foundation). We will discuss the underlying principles of REST (Representational State Transfer), the motivations for it, and how to create that style of web service using WCF. We will discuss how you can secure these type of web services and finally, we will briefly touch on ADO.NET Data Services (formerly known as Astoria): a technology that automatically exposes a REST web service layer on top of a Linq data source.

Rendering a diff in a web page

Recently in my spare time, I have been working on a web site where developers can post code snippets which they would like to be reviewed by other developers on the web.  Once a code snippet has been posted, other developers can add comments and make a revised version of the code snippet, which will be rendered under the original code snippet as a diff (the difference between the original snippet and the revised snippet).  I have registered myself a domain, but the site is not ready to be launched yet.  This post is about how I rendered a line-oriented diff in an ASP.NET MVC web page.  I'll write another post to announce the site itself when I launch it.

The diffUI

 

The Algorithm

After a brief period of trying to figure out my own algorithm for rendering a diff, I decided to do some research on how others have solved the problem.  It seems that it's not quite as easy of a problem to solve as it would seem on the surface, and there has actually been a fair amount of in-depth research done on how to solve it.  I wound up deciding to use Bill Menee's Menees diff library, which uses the popular Myers algorithm, described in the paper An O(ND) Difference Algorithm and its Variations by Eugene W. Myers.  The paper looks fascinating, and I might be interested in implementing it myself at some point, but for the moment I'm going to use the Menees library.  Here is the abstract to the paper, to give you an overview of how Myers solves the problem.

The problems of finding a longest common subsequence of two sequences A and B and a shortest edit script
for transforming A into B have long been known to be dual problems. In this paper, they are shown to be
equivalent to finding a shortest/longest path in an edit graph. Using this perspective, a simple O(ND) time
and space algorithm is developed where N is the sum of the lengths of A and B and D is the size of the
minimum edit script for A and B. The algorithm performs well when differences are small (sequences are
similar) and is consequently fast in typical applications. The algorithm is shown to have O(N+D2 )
expected-time performance under a basic stochastic model. A refinement of the algorithm requires only
O(N) space, and the use of suffix trees leads to an O(NlgN +D2 ) time variation.

The Code

Here's the code that I came up with to render a diff created by the Menees library.

The model and view-data:

   1:  using System;
   2:  using System.Collections;
   3:  using System.Collections.Generic;
   4:  using System.Linq;
   5:  using System.Text;
   6:  using System.Web;
   7:   
   8:  using Menees.DiffUtils;
   9:   
  10:  namespace CodeReview.Models
  11:  {
  12:      /// <summary>
  13:      /// Represents a chunk of source code
  14:      /// </summary>
  15:      public class SourceCode
  16:      {
  17:          public int Id { get; set; }
  18:   
  19:          private string _title = string.Empty;
  20:   
  21:          public string Title 
  22:          {
  23:              get
  24:              {
  25:                  return _title;
  26:              }
  27:              set
  28:              {
  29:                  _title = value;
  30:              }
  31:          }
  32:   
  33:          public string Description { get; set; }
  34:          public string Code { get; set; }
  35:          public string CreatedBy { get; set; }
  36:          public List<Comment> Comments { get; set; }
  37:   
  38:          private List<string> _lines = null;
  39:          
  40:          public List<string> Lines
  41:          {
  42:              get
  43:              {
  44:                  if (_lines == null)
  45:                      _lines = GetLines(Code);
  46:   
  47:                  return _lines;
  48:              }
  49:          }
  50:   
  51:          public bool IsValid
  52:          {
  53:              get { return (GetRuleViolations().Count() == 0); }
  54:          }
  55:   
  56:          public SourceCode(string code)
  57:              : this()
  58:          {
  59:              Code = code;
  60:          }
  61:   
  62:          public SourceCode()
  63:          {
  64:              Comments = new List<Comment>();
  65:          }
  66:   
  67:          public List<Difference> Diff(SourceCode compareWith)
  68:          {
  69:              List<Difference> differences = new List<Difference>();
  70:              IList a = Lines;
  71:              IList b = compareWith.Lines;
  72:              TextDiff diff = new TextDiff((int)HashType.HashCode, false, false, 0);
  73:              EditScript script = diff.Execute(a, b);
  74:   
  75:              foreach (Edit edit in script)
  76:              {
  77:                  differences.Add(ConvertEditToDifference(edit, compareWith));
  78:              }
  79:   
  80:              return differences;
  81:          }
  82:   
  83:          private List<string> GetLines(string code)
  84:          {
  85:              return code.Split('\n').ToList();
  86:          }
  87:   
  88:          private Difference ConvertEditToDifference(Edit edit, SourceCode compareWith)
  89:          {
  90:              return new Difference()
  91:              {
  92:                  LineNumber = edit.StartA + 1,
  93:                  Text = GetChangeText(edit, compareWith),
  94:                  Type = ConvertEditTypeToDifferenceType(edit.Type),
  95:                  NumberOfLines = edit.Length
  96:              };
  97:          }
  98:   
  99:          private string GetChangeText(Edit edit, SourceCode compareWith)
 100:          {
 101:              StringBuilder changeText = new StringBuilder();
 102:              SourceCode textSource;
 103:              int lineNumberInTextSource;
 104:   
 105:              if (edit.Type == EditType.Insert)
 106:              {
 107:                  textSource = compareWith;
 108:                  lineNumberInTextSource = edit.StartB;
 109:              }
 110:              else
 111:              {
 112:                  textSource = this;
 113:                  lineNumberInTextSource = edit.StartA;
 114:              }
 115:   
 116:              for (int i = 0; i < edit.Length; i++)
 117:              {
 118:                  changeText.Append(textSource.Lines[lineNumberInTextSource + i]);
 119:              }
 120:   
 121:              return changeText.ToString();
 122:          }
 123:   
 124:          private DifferenceType ConvertEditTypeToDifferenceType(EditType editType)
 125:          {
 126:              switch (editType)
 127:              {
 128:                  case EditType.Change:
 129:                      return DifferenceType.Change;
 130:                  case EditType.Delete:
 131:                      return DifferenceType.Removal;
 132:                  case EditType.Insert:
 133:                      return DifferenceType.Addition;
 134:                  default:
 135:                      throw new ArgumentException("Unexpected edit type: " + editType, "editType");
 136:              }
 137:          }
 138:   
 139:          public IEnumerable<RuleViolation> GetRuleViolations()
 140:          {
 141:              if (String.IsNullOrEmpty(Title))
 142:                  yield return new RuleViolation("Title is required", "Title");
 143:   
 144:              if (String.IsNullOrEmpty(Code))
 145:                  yield return new RuleViolation("Code is required", "Code");
 146:   
 147:              yield break;
 148:          }
 149:   
 150:          private void Validate()
 151:          {
 152:              if (!IsValid)
 153:                  throw new ApplicationException("Rule violations prevent saving");
 154:          }
 155:      }
 156:   
 157:      public class Difference
 158:      {
 159:          public DifferenceType Type { get; set; }
 160:          public string Text { get; set; }
 161:          public int LineNumber { get; set; }
 162:          public int NumberOfLines { get; set; }
 163:      }
 164:  }
 165:   
 166:  namespace CodeReview.ViewData
 167:  {
 168:      public class ShowSourceCodeViewData
 169:      {
 170:          public string RenderedOriginal { get; set; }
 171:          public List<CommentViewData> Comments { get; set; }
 172:   
 173:          public ShowSourceCodeViewData()
 174:          {
 175:              Comments = new List<CommentViewData>();
 176:          }
 177:      }
 178:   
 179:      public class CommentViewData
 180:      {
 181:          public string RenderedOriginal { get; set; }
 182:          public string RenderedRevised { get; set; }
 183:          public string Text { get; set; }
 184:      }
 185:  }
 186:   

The controller action and supporting methods:

   1:          public ActionResult Show(int? id)
   2:          {
   3:              SourceCode original = _repository.GetById(id.Value);
   4:              string[] originalLines = original.Lines.ToArray();
   5:              ShowSourceCodeViewData viewData = new ShowSourceCodeViewData();
   6:   
   7:              viewData.RenderedOriginal = ConvertFromPlainTextToHtml(original.Code);
   8:   
   9:              foreach (Comment comment in original.Comments)
  10:              {
  11:                  StringBuilder originalBuilder = new StringBuilder(); //the buffer for displaying the original version (on the left side of the diff)
  12:                  StringBuilder editedBuilder = new StringBuilder(); //the buffer for displaying the revised version (on the right side of the diff)
  13:                  string[] editedLines = comment.Revision.Lines.ToArray();
  14:                  List<Difference> differences = original.Diff(comment.Revision);
  15:                  int originalLineNumber = 0;
  16:                  int editedLineNumber = 0;
  17:                  CommentViewData commentViewData = new CommentViewData();
  18:   
  19:                  foreach (Difference difference in differences)
  20:                  {
  21:                      //put all unchanged lines leading up to this difference into both buffers
  22:                      for (; originalLineNumber < (difference.LineNumber - 1); originalLineNumber++, editedLineNumber++)
  23:                      {
  24:                          originalBuilder.Append(RenderLineHtml(DifferenceType.NoChange, originalLineNumber, originalLines[originalLineNumber]));
  25:                          editedBuilder.Append(RenderLineHtml(DifferenceType.NoChange, editedLineNumber, editedLines[editedLineNumber]));
  26:                      }
  27:   
  28:                      if (difference.Type == DifferenceType.Addition)
  29:                      {
  30:                          string differenceText = difference.Text;
  31:   
  32:                          if (differenceText.EndsWith("\r"))
  33:                              differenceText = differenceText.Substring(0, differenceText.Length - 1);
  34:   
  35:                          string[] lines = differenceText.Split('\r');
  36:   
  37:                          //put added lines only into the buffer for the revised version, and add blank space to the buffer for the original version
  38:                          foreach (string line in lines)
  39:                          {
  40:                              editedBuilder.Append(RenderLineHtml(difference.Type, editedLineNumber, line));
  41:                              originalBuilder.AppendLine();
  42:                              editedLineNumber++;
  43:                          }
  44:                      }
  45:   
  46:                      if (difference.Type == DifferenceType.Removal)
  47:                      {
  48:                          string differenceText = difference.Text;
  49:   
  50:                          if (differenceText.EndsWith("\r"))
  51:                              differenceText = differenceText.Substring(0, differenceText.Length - 1);
  52:   
  53:                          string[] lines = differenceText.Split('\r');
  54:   
  55:                          //put removed lines only into the buffer for the original version, and add blank space to the buffer for the revised version
  56:                          foreach (string line in lines)
  57:                          {
  58:                              originalBuilder.Append(RenderLineHtml(difference.Type, originalLineNumber, line));
  59:                              editedBuilder.AppendLine();
  60:                              originalLineNumber++;
  61:                          }
  62:                      }
  63:   
  64:                      if (difference.Type == DifferenceType.Change)
  65:                      {
  66:                          //put revised version of changed line into buffer for revised version
  67:                          editedBuilder.Append(RenderLineHtml(difference.Type, editedLineNumber, editedLines[editedLineNumber]));
  68:                          //put original version of changed line into buffer for original version
  69:                          originalBuilder.Append(RenderLineHtml(difference.Type, originalLineNumber, difference.Text));
  70:                          editedLineNumber++;
  71:                          originalLineNumber++;
  72:                      }
  73:                  }
  74:   
  75:                  //put all remaining unchanged lines into buffer for original version
  76:                  for (int i = originalLineNumber; i < originalLines.Length; i++)
  77:                      originalBuilder.Append(RenderLineHtml(DifferenceType.NoChange, i, originalLines[i]));
  78:   
  79:                  //put all remaining unchanged lines into buffer for revised version
  80:                  for (int i = editedLineNumber; i < editedLines.Length; i++)
  81:                      editedBuilder.Append(RenderLineHtml(DifferenceType.NoChange, i, editedLines[i]));
  82:   
  83:                  commentViewData.RenderedOriginal = ConvertFromPlainTextToHtml(originalBuilder.ToString());
  84:                  commentViewData.RenderedRevised = ConvertFromPlainTextToHtml(editedBuilder.ToString());
  85:                  commentViewData.Text = comment.Text;
  86:   
  87:                  viewData.Comments.Add(commentViewData);
  88:              }
  89:   
  90:              return View(viewData);
  91:          }
  92:   
  93:          private string ConvertFromPlainTextToHtml(string plainText)
  94:          {
  95:              return plainText.Replace("\r", "<br/>").Replace("\n", "").Replace("  ", "&nbsp;&nbsp;");
  96:          }
  97:   
  98:          private string RenderLineHtml(DifferenceType differenceType, int lineNumber, string lineText)
  99:          {
 100:              //the line will be styled according to the type of change that was made
 101:              return string.Format("<div class=\"{0}\">{1}: {2}</div>", differenceType.ToString().ToLower(), lineNumber, lineText);
 102:          }

 

The view:

   1:  <%
   2:      foreach (CommentViewData comment in Model.Comments)
   3:      {
   4:  %>
   5:      <p class="comment_body">
   6:          <%= comment.Text %>
   7:      </p>
   8:      <div class="editingArea">
   9:          <div class="originalCode">
  10:              Original:<br />
  11:              <%= comment.RenderedOriginal %>
  12:          </div>
  13:          <div class="editedCode">
  14:              Edited:<br />
  15:              <%= comment.RenderedRevised %>
  16:          </div>
  17:          <div style="height: 500px"></div>
  18:      </div>
  19:  <%
  20:      }
  21:  %>
 
The CSS:

.originalCode { float: left; border: thin solid black; padding: 10px 10px 10px 10px; width: 450px; }

.editedCode { float: right; border: thin solid black; padding: 10px 10px 10px 10px; position: absolute; left: 600px; width: 450px; }

.editingArea { }

.addition { background-color: Black; color: green }

.removal { background-color: Black; color: red }

.change { background-color: Black; color: white }

.comment_body { font-style: italic; font-weight: bold; }

.comment_header { font-weight: bold; }

#Title { width: 700px; }

#Description { width: 700px; height: 150px; }

#Code { width: 700px; height: 300px; }

This is going to need some work before it's ready for prime time (the HTML conversion and CSS need work, and the controller method should be broken out into smaller methods, perhaps using a command pattern or something, etc.), but I figured it was enough to get you going if you're thinking about implementing something like this yourself.  (This approach could make for some very nice build notification emails, IMO.)  Did I leave anything important out?  Is there a better or easier way to do this?  Is there a glaring bug?  Leave a comment!

del.icio.us Tags: ,,,,

Upgrading our site to MVC RC

At work, we have a web application implemented using the ASP.NET MVC Framework.  This week, I upgraded that site from MVC beta to MVC Release Candidate.  The release notes had some important information about editing the web.config so that you don't need a code-behind for your views, but all it tells you about upgrading the rest of your code-base is that you should upgrade the MVC assemblies that your application is referencing and then fix the compiler errors.  Even after reading about the various changes in the RC, I still had a little trouble figuring out how to adapt our code to work with the various breaking changes, so I figured I would outline some of the issues I ran into here, and how I resolved them, to save you some time if you haven't gotten around to doing this upgrade yet.  Here goes...

  • ModelBinderResult no longer exists.  BindModel() should now take a ControllerContext as well as a ModelBindingContext parameter, and it should return the model itself, rather than wrapping it in a ModelBinderResult
  • IValueProvider went away - use FormCollection instead to retrieve form values
  • The value provider parameter to UpdateModel is now of type IDictionary<string, ValueProviderResult>, rather than IValueProvider (since IValueProvider no longer exists).  You must call ToValueProvider() on your FormCollection and pass that to UpdateModel.
  • ModelBindingContext no longer has a "Controller" property.  If you're trying to access that from the BindModel method in a model binder, access it from the new ControllerContext parameter instead.
  • ModelBindingContext.ValueProvider is now of type IDictionary<string, ValueProviderResult> (not IValueProvider since that no longer exists), so any places where you're calling GetValue() on that, you will need to replace it with a call to the indexer (eg. bindingContext.ValueProvider[fieldName] instead of bindingContext.ValueProvider.GetValue(fieldName)).  Also note that when you attempt to obtain a value from a generic Dictionary for a key that does not exist, you will get a KeyNotFoundException, whereas GetValue() simply returned null, so you will need to add a check that the key exists before retrieving a value from the collection.
  • The ModelBindingContext constructor now takes zero arguments instead of seven.  If you're instantiating a ModelBindingContext in a unit test, rather than passing everything in on the constructor, you'll want to set
    the ValueProvider, ModelType and Model properties (the controller is no longer set on the ModelBindingContext - its set in the ControllerContext, and all the other constructor parameters we were passing null for).
  • RedirectToRouteResult.Values is now RedirectToRouteResult.RouteValues

 

Good luck and God speed!

 

del.icio.us Tags: ,,,,

Migrating TFS: Point your build server at the new TFS server

The Migration

Today I finished migrating TFS from a VM to a newly re-purposed machine.  The VM was on an overloaded host box and TFS was a bit sluggish.  Yesterday, I followed Microsoft's painstaking instructions on How to Move Your Team Foundation Server from One Hardware Configuration To Another.  This morning I updated our TFS build scripts so that all the file paths were valid on the new box, and then I refreshed the Version Control Cache on the build server by using the "tf workspaces /s:http://ApplicationTierServerName:Port" command (the colon after the "/s" is a needed variation on the command given in Microsoft's instructions on How to Refresh the Data Caches on Client Computers).  To my dismay, when I ran a build, it failed almost immediately with an error indicating it was trying to access something like vstfs:///Version_Control/Versioned_Items, and the build notification window on the build server still showed the old TFS server name.  I poked around the various TFS databases and a few other places to see if I could find the old server name, but I did not (or at least not anywhere relevant).  I decided to have a look at the .config file being used by the Visual Studio Team Foundation Build service executable, and there was a setting in there for a TFS server URI, but the value was blank.  The comment above the setting indicates that it overrides the setting in the registry under "HKCU".  I figured that if the old server name was in the registry, I would prefer to update that than override it with the new server name.  (No point in having old server names lurking around.)

Re-configuring TFS Build

After a quick search in the registry (using regedt32 of course), I found the "AllowedTeamServer" key in HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\9.0\TeamFoundation\Build\Service, and it was set to the old TFS server URI.  I updated this setting (always exercise caution when modifying your registry!) to be the URI of the new TFS server (http://[server]:8080/), and then I re-started the Visual Studio Team Foundation Build service, and voila! the next build I kicked off succeeded.  Unfortunately the build notification window on the build server still shows the old TFS server name, but I don't anticipate that being a problem.

Retrospective

Over-all, Microsoft's aforementioned instructions for migrating TFS to a new server worked pretty well (after muddling through the usual smattering of security issues), but the fact that properly re-configuring TFS Build to point to a new TFS server (which one would think would be a somewhat common operation, if not frequent, per se) requires a "reg hack" is a little annoying to me, and the fact that this step is not documented in the migration instructions is a significant over-sight.

BDD-style feature tests using IronRuby and RSpec/Cucumber

Introduction

BDD (Behavior Driven Development) is a specialization of TDD (Test Driven Development) that encapsulates a set of best practices espoused by the most successful TDD gurus.  The main theme is focusing on behavior rather than implementation in your tests.  A great tool for verifying behavior (writing BDD-style tests) is RSpec, and now it can be used to test .NET code.

To get more information on BDD, read Dan North's Introduction to BDD.  To get some background information on TDD, read Scott Bellware's classic Red-Green-Refactor post or read Kent Beck's TDD By Example (this one's on my to-do list, but I've heard great things about it).

RSpec is a unit testing tool for Ruby that provides an internal DSL which supports nice BDD-style specifications.  An example of that syntax is below.

 

# bowling_spec.rb
require 'bowling'  

describe Bowling do
  before(:each) do
    @bowling = Bowling.new
  end

  it "should score 0 for gutter game" do
    20.times { @bowling.hit(0) }
    @bowling.score.should == 0
  end
end

RSpec also provides an external DSL (the RSpec Story Runner) that allows you to create executable plain-English feature documentation in the Given/When/Then format typical of BDD tests.  This tool has been broken out into its own project called Cucumber, and an example of the syntax is below (from the RSpec website).

 

Feature: transfer from savings to checking account
  As a savings account holder
  I want to transfer money from my savings account to my checking account
  So that I can get cash easily from an ATM



  Scenario: savings account has sufficient funds
    Given my savings account balance is $100
    And my checking account balance is $10
    When I transfer $20 from savings to checking
    Then my savings account balance should be $80
    And my checking account balance should be $30
 
  Scenario: savings account has insufficient funds
    Given my savings account balance is $50
    And my checking account balance is $10
    When I transfer $60 from savings to checking
    Then my savings account balance should be $50
    And my checking account balance should be $10

 

As the RSpec website says "Each Given, When and Then is a Step. The Ands are each the same kind as the previous Step. Steps get defined in Ruby like this (detail left out for brevity) in steps.rb (in the same directory in this example):"

 

Given /^my (.*) account balance is \$(\d+)$/ do |account_type, amount|
  create_account(account_type, amount)
end

When /^I transfer \$(\d+) from (.*) to (.*)$/ do |amount, source_account,
target_account|
  get_account(source_account).transfer(amount).to(get_account(target_account))
end

 

Then /^my (.*) account balance should be \$(\d+)$/ do |account_type, amount|
  get_account(account_type).should have_a_balance_of(amount)
end

 

Getting RSpec and Cucumber set up to work with .NET

The first thing you need to do to start verifying the behavior of .NET code using RSpec is to install IronRuby.  In theory, it's also possible to use RSpec to test .NET code by using jRuby and Java's interoperability with .NET, and in fact there's an example of that approach that comes bundled with Cucumber, but I thought this was one too many layers of interoperability (jRuby -> Java -> .NET) for an approach I am going to attempt to use to verify behavior in the majority of code I write.  Also, Cucumber's web site says "When IronRuby matures it can be used to 'test' .NET code too", so I took that as a hint that the jRuby way might be problematic in the long run.  Finally, I wanted an excuse to play with IronRuby :-)  To get the latest version of IronRuby, you must install TortoiseSVN and then do an SVN Checkout from http://ironruby.rubyforge.org/svn/trunk.  Open IronRuby.sln in Visual Studio (telling it to "Load Projects Normally" if prompted) and Build Solution.  There is a ZIP of IronRuby you can download from rubyforge also, but that didn't work too well for me, so I wouldn't recommend it.  (I suspect it's a significantly outdated release.)

In order to obtain Cucumber and all its dependencies, download and install the latest version of the Ruby One-Click Installer (henceforth referred to as regular Ruby).  At the command line (from any folder), type "gem Cucumber" and answer "Y" when it asks you to install each of the dependencies.  The reason that regular Ruby (rather than IronRuby) is used for this step is because I was not able to get RubyGems to work on IronRuby.

Next, copy the contents of all the gems you just downloaded from C:\ruby\lib\ruby\gems\1.8\gems (assuming you installed regular Ruby to C:\ruby) to C:\Projects\IronRuby\trunk\lib (assuming you checked out the IronRuby trunk to C:\Projects\IronRuby\trunk).  A list of the gems you will need to copy is as follows:

cucumber
hoe
polyglot
rake
rubyforge
rspec
term-ansicolor
treetop

Note that you will specifically want to copy only the contents of the "lib" directory of each of these gems.  (For example, C:\ruby\lib\ruby\gems\1.8\gems\treetop-1.2.4\lib\treetop.rb will be copied to C:\Projects\IronRuby\trunk\lib\treetop.rb and similarly the C:\ruby\lib\ruby\gems\1.8\gems\treetop-1.2.4\lib\treetop folder will be copied to C:\Projects\IronRuby\trunk\lib\treetop.)  Copying the contents of the "lib" folder of each of these gems was the only way I could manage to get IronRuby to recognize all of them at the same time.  I have heard that setting the GEM_PATH environment variable to the location where you have put your gems will enable IronRuby to recognize them, but that didn't work for me, which necessitated the kludgy step I just described.

Next, you will need to modify a few files within RSpec and Cucumber to get them to work with IronRuby.  the modifications are as follows (paths are relative to C:\Projects\IronRuby\trunk):

  • In lib\cucumber\formatters\pretty_formatter.rb, find the "source_comment" method, comment out the body of it, and add simply two double-quotes (an empty string) as the new body of the method.  This is because the executable file location doesn't seem to be available in IronRuby.  (The commented out code is what would normally print the name of the file and the line number of each code definition referred to by the specification.)
  • In lib\cucumber\formatters\ansicolor.rb, comment out the first line that says "gem 'term-ansicolor'".  (I wasn't able to get ANSI color to work for the output from Cucumber.)
  • In lib\cucumber\tree\step.rb, find the "execute_in" method and then find the following code:

method_line_pos = e.backtrace.index(method_line)
if method_line_pos
  strip_pos = method_line_pos - (Pending === e ? PENDING_ADJUSTMENT : REGULAR_ADJUSTMENT)
else
  # This happens with rails, because they screw up the backtrace
  # before we get here (injecting erb stactrace and such)
end
format_error(strip_pos, proc, e)

  • and change it to:

if e.backtrace
  method_line_pos = e.backtrace.index(method_line)
  if method_line_pos
    strip_pos = method_line_pos - (Pending === e ? PENDING_ADJUSTMENT : REGULAR_ADJUSTMENT)
  else
    # This happens with rails, because they screw up the backtrace
    # before we get here (injecting erb stactrace and such)
  end
  format_error(strip_pos, proc, e)
else
  e.extra_data = format_error2(proc, e)
  raise e
end         

  • In lib\spec\expectations\errors.rb, add two properties to the "ExpectationNotMetError" class as follows (this step and the last step are necessary because the file name and line are not included with the Exception message in IronRuby as they are in regular Ruby):

def extra_data=(value)
  @extra_data = value
end

def message
  to_s + "\n" + @extra_data
end

 

Testing .NET code

To complete the test, I created a simple C# source file to test called "Accent.cs" as follows:

namespace TestLibrary
{
    public class Accent
    {
        private readonly string _stateAbbreviation;

        public Accent(string stateAbbreviation)
        {
            _stateAbbreviation = stateAbbreviation;
        }

        public string PronounceWord(string word)
        {
            if (_stateAbbreviation == "MA")
            {
                switch (word)
                {
                    case "bar":
                        return "bah";
                    case "dollar":
                        return "dolla";
                }
            }

            return word;
        }
    }
}

I compiled that source file into an assembly called "TestLibrary.dll" and copied it (and TestLibrary.pdb) to a new folder: C:\Projects\IronRuby\trunk\lib\lib.

Next, I created a file called cucumber.yml in C:\Projects\IronRuby\trunk\lib with the following contents (copied from the "calculator" example provided in Cucumber):

default: --format pretty features

I also created another file (also copied from the "calculator" example) called Rakefile with the following contents:

$:.unshift(File.dirname(__FILE__) + '/../../lib')
require 'cucumber/rake/task'

Cucumber::Rake::Task.new do |t|
  t.cucumber_opts = "--profile default"
end

Next, I created a file called pronunciation.feature (a specification) in a new folder: C:\Projects\IronRuby\trunk\lib\features with the following contents:

Feature: Pronunciation
  In order to gain the trust of a customer
  As a sales representative
  I want to pronounce words in the dialect of the customer

  Scenario: Pronounce a word
    Given My client lives in MA
    When I pronounce bar
    Then the word should be pronounced bah

  Scenario: Pronounce a word
    Given My client lives in CA
    When I pronounce bar
    Then the word should be pronounced bar

  Scenario: Pronounce a word
    Given My client lives in MA
    When I pronounce dollar
    Then the word should be pronounced dolla

  Scenario: Pronounce a word
    Given My client lives in CA
    When I pronounce dollar
    Then the word should be pronounced dollar

Next, I created a file called proncuation_steps.rb (defining the steps in the specification above) in a new folder: C:\Projects\IronRuby\trunk\lib\features\steps with the following contents:

require 'spec'
$:.unshift(File.dirname(__FILE__) + '/../../lib')
require 'mscorlib'
require 'TestLibrary'

Before do
end

After do
end

Given "My client lives in $state" do |state|
    @accent = TestLibrary::Accent.new state
end

When /I pronounce (\w+)/ do |word|
  @result = @accent.PronounceWord word
end

Then /the word should be pronounced (.*)/ do |result|
  @result.to_s.should == result.to_s
end

Finally, I created two files in C:\Projects\IronRuby\trunk\build\debug. The first of them is icuc.rb, which contains the following:

require 'cucumber'
require 'cucumber/cli'

Cucumber::CLI.execute

The second file is icuc.bat, which contains the following:

@echo off
set IRONRUBY=c:\Projects\IronRuby\trunk
pushd %IRONRUBY%\lib
%IRONRUBY%\build\Debug\ir %IRONRUBY%\build\Debug\icuc.rb
popd
set IRONRUBY=

These two files are the equivalent of the "cucumber" and "cucumber.cmd" files in C:\ruby\bin.  You can now type "icuc" at a command prompt and it will run the Cucumber test you just created, which should pass!  I chose the name "icuc" so that it wouldn't conflict with the "cucumber" command in regular Ruby.  Happy testing, er... I mean verifying! ;-)

If you have any trouble getting this to work for you, or if you know of a better way to do it, please leave a comment!  I know this method is less than ideal, so I'm hoping one of you can help me improve it. :-)