But it works on my PC!

The random thoughts of Richard Fennell on technology and software development

DDD Dublin 2010 Sessions published

The results of the DDD Dublin vote is in, and I am sorry to say I did not make it through the selection process. I must come up with at least some more interesting titles and abstracts (of course I can leave the sessions as dull as ever as you have voted for them by then, just like politicians and general elections really).

The schedule looks really good, but after a bit of thought I have decided not to attend the event; I am speaking or attending events both the week before and  the week after DDD Dublin so I am going to take the chance to reduce my carbon footprint and have a weekend at home.

Upcoming speaking engagements

and that is just this year

[Updated 22nd & 29th Sep 2010]

DDD Dublin voting as opened

The vote has opened for DDD Ireland, get in there an vote for the sessions you would like to see, there is a nice selection.

May I draw your attention to the two I have submitted

  • How can I add my own custom step to a TFS 2010 build, or do I even need to try? (Oooh a new one!)
  • Developing Testable Web Parts for SharePoint (Could also be called Using Typemock with SharePoint)

No pressure…….

Experiences running multiple instances of 2010 build service on a single VM

I think my biggest issue with TFS2010 is the problem that a build controller is tied to a single Team Project Collection (TPC). For a company like mine where we run a TPC for each client this means we have had to start to generate a good number of virtualised build controller/agents. It is especially irritating as I know that the volume of builds on any given controller is low.

A while ago Jim Lamb blogged about how you could define multiple build services on a single box, but the post was full caveats on how it was not supported/recommended etc. Well since this post there has been some discussion on this technique and I think the general feeling is, yes it is not supported, but there is no reason it will not function perfectly well as long as you consider some basic limitations:

  1. The two build controllers don’t know about each other, so you can easily have two build running at the same time, this will have an unpredictable effect on performance.
  2. You have to make sure that the two instances don’t share any workspace disk locations, else they will potentially start overwriting each other
  3. Remember building code is usually IO locked not CPU locked, so when creating your build system think a lot about the disk, throwing memory and CPU will have little effect. The fact we run our build services on VMs and these us a SAN should mitigate much of this potential issue.
  4. The default when you install a controller/agent on a box is for one agent to be created for each core on the box. This rule is still a good idea, but if you are installing two controller/agent sets on a box make sure you don’t define more agents than cores (for me this means on by build VM I have to 2 virtual CPUs as I am running 2 controller/agent pairs)

Jims instructions are straight forward, but I did hit a couple of snags:

  • When you enter the command line to create the instance, make sure there a spaces after the equals for the parameters, else you get an error

sc.exe create buildMachine-collection2 binpath= "C:\Program Files\Microsoft Team Foundation Server 2010\Tools\TfsBuildServiceHost.exe /NamedInstance:buildMachine-collection2" DisplayName= "Visual Studio Team Foundation Build Service Host (Collection2)"

  • I cannot stress enough how important it is give the new instances sensible names, especially as their numbers grow. Jim suggested naming after the TPC they service, for me this is bad move as at any given time were are working for a fairly small number of clients, but the list is changing as projects start and stop. It is therefore easier for me to name a controller for the machine is it hosted on as they will be reassigned between TPC based on need. So I settle on the names in the form ‘build1-collection2’ not a TPC base done. These are easy to associate with the VMs in use when you see them in VS2010
  • When I first tried to get this all up and ran the admin console for the command prompt I got the error shown below

image

After a bit of retyping this went away. I think it was down to stray spaces at end of SET variable, but not 100% sure over this. I would just make sure you strings match if you see this problem.

[Updated 26 Nov 2010] The batch file to start the management console is in the form

      set TFSBUILDSERVICEHOST=buildMachine-collection2 
      "C:\Program Files\Microsoft Team Foundation Server 2010\Tools\tfsmgmt.exe"

Make sure that you run this batch file as administration (right click run as admin) if you don't the management console picks up the default instance

  • Also it is a good Idea to go into the PCs service and make sure your new build service instance is set to auto start, to avoid surprises on a reboot.
  • When you configure the new instance make sure you alter the port it runs on (red box below) I am just incrementing it for each new instance e.g. 9191 –> 9192. If you don’t alter this the service will not start as it’s endpoint will already be in use.
  • Also remember to set the identity of the build service run as (green box), usually [Domain]\TFSBuild, too easy to forget as well as you click through the create dialogs.

image

Once this is set you can start the service and configure the controller and agent(s) exactly as normal.

You might want to consider how the workspace is mapped to the your multiple controllers, so you use different root directories, but that is your call. Thus far leaving it all as it was when I was using a separate VM for each build is working fine for me.

We shall see how many services I can put onto single VM, but it is certainly something I don’t want to push to hard. However that said if you are like use with a relatively low load on the build system this has to be worth looking at to avoid proliferation of build VMs.

Stupid mistake over Javascript parameters

I have been using the Google Maps JavaScript API today. I lost too much time over a really stupid error. I was trying to set the zoom level on a map using the call

map.setZoom(<number>);

I had set my initial zoom level to 5 (the scale is 1-17 I think) in the map load, when I called setZoom to 11 all was fine, but if I set it to any other number is reverted to 5. This different effect for different numbers was a real red herring. The problem was down to how I was handling the variable containing the zoom level prior to passing it to setZoom method. When it was set to 11 it was set explicitly e.g.

var zoomNumber = 11;

However when it was any other value it was being pulled from the value property of a combo box, so was actually a string. My problem was that setZoom does not return an error if if pass something in it does not understand, it just reverts to it’s initial value.

The solution was simple, cast the value to a string and it works as expected

map.setZoom(parseInt(ZoomNumber));

Problem faking multiple SPLists with Typemock Isolator in a single test

I have found a problem with repeated calls to indexed SharePoint Lists with Typemock Isolator 6.0.3. This what I am trying to do…

The Problem

I am using Typemock Isolator to allow me to develop a SharePoint Webpart outside of the SharePoint environment  (there is a video about this on the Typemock site). My SharePoint Webpart uses data drawn from a pair of SharePoint lists to draw a map using Google maps API; so in my test harness web site page I have the following code in the constructor that fakes out the two SPLists and populates them with test content.

 

   1: public partial class TestPage : System.Web.UI.Page
   2:  {
   3:     public TestPage()
   4:     {
   5:  
   6:        var fakeWeb = Isolate.Fake.Instance<SPWeb>();
   7:        Isolate.WhenCalled(() => SPControl.GetContextWeb(null)).WillReturn(fakeWeb);
   8:  
   9:        // return value for 1st call
  10:        Isolate.WhenCalled(() => fakeWeb.Lists["Centre Locations"].Items).WillReturnCollectionValuesOf(CreateCentreList());
  11:        // return value for all other calls
  12:        Isolate.WhenCalled(() => fakeWeb.Lists["Map Zoom Areas"].Items).WillReturnCollectionValuesOf(CreateZoomAreaList());
  13:     }
  14:  
  15:     private static List<SPListItem> CreateZoomAreaList()
  16:     {
  17:        var fakeZoomAreas = new List<SPListItem>();
  18:        fakeZoomAreas.Add(CreateZoomAreaSPListItem("London", 51.49275, -0.137722222, 2, 14));
  19:        return fakeZoomAreas;
  20:     }
  21:  
  22:     private static List<SPListItem> CreateCentreList()
  23:     {
  24:        var fakeSites = new List<SPListItem>();
  25:        fakeSites.Add(CreateCentreSPListItem("Aberdeen ", "1 The Road,  Aberdeen ", "Aberdeen@test.com", "www.Aberdeen.test.com", "1111", "2222", 57.13994444, -2.113333333));
  26:        fakeSites.Add(CreateCentreSPListItem("Altrincham ", "1 The Road,  Altrincham ", "Altrincham@test.com", "www.Altrincham.test.com", "3333", "4444", 53.38977778, -2.349916667));
  27:        return fakeSites;
  28:     }
  29:  
  30:     private static SPListItem CreateCentreSPListItem(string title, string address, string email, string url, string telephone, string fax, double lat, double lng)
  31:     {
  32:         var fakeItem = Isolate.Fake.Instance<SPListItem>();
  33:         Isolate.WhenCalled(() => fakeItem["Title"]).WillReturn(title);
  34:         Isolate.WhenCalled(() => fakeItem["Address"]).WillReturn(address);
  35:         Isolate.WhenCalled(() => fakeItem["Email Address"]).WillReturn(email);
  36:         Isolate.WhenCalled(() => fakeItem["Site URL"]).WillReturn(url);
  37:         Isolate.WhenCalled(() => fakeItem["Telephone"]).WillReturn(telephone);
  38:         Isolate.WhenCalled(() => fakeItem["Fax"]).WillReturn(fax);
  39:         Isolate.WhenCalled(() => fakeItem["Latitude"]).WillReturn(lat.ToString());
  40:         Isolate.WhenCalled(() => fakeItem["Longitude"]).WillReturn(lng.ToString());
  41:         return fakeItem;
  42:     }
  43:  
  44:     private static SPListItem CreateZoomAreaSPListItem(string areaName, double lat, double lng, double radius, int zoom)
  45:     {
  46:         var fakeItem = Isolate.Fake.Instance<SPListItem>();
  47:         Isolate.WhenCalled(() => fakeItem["Title"]).WillReturn(areaName);
  48:         Isolate.WhenCalled(() => fakeItem["Latitude"]).WillReturn(lat.ToString());
  49:         Isolate.WhenCalled(() => fakeItem["Longitude"]).WillReturn(lng.ToString());
  50:         Isolate.WhenCalled(() => fakeItem["Radius"]).WillReturn(radius.ToString());
  51:         Isolate.WhenCalled(() => fakeItem["Zoom"]).WillReturn(zoom.ToString());
  52:         return fakeItem;
  53:     }
  54:  
  55: }
  56:  

The problem is that if I place the following logic in my Webpart

   1: SPWeb web = SPControl.GetContextWeb(Context);
   2: Debug.WriteLine (web.Lists["Centre Locations"].Items.Count);
   3: Debug.WriteLine (web.Lists["Map Zoom Areas"].Items.Count);

I would expect this code to return

2
1

But I get

1
1

If I reverse two Isolate.WhenCalled lines in the constructor I get

2
2

So basically only the last Isolate.WhenCalled is being used, this is not what I expect from the Typemock documentation. .This states that, worst case, the first Isolate.WhenCalled should be used for the first call and the second for all subsequent calls, and actually the index string should be used to differentiate anyway. This is obviously not working. I actually also tried using null in place of the both the index strings and got the same result.

A Workaround

I have managed to workaround this problem with a refactor of my code. In my web part I used to moved all the SPList logic into a pair of methods

   1: private List<GISPoint> LoadFixedMarkersFromSharepoint(SPWeb web, string listName)
   2: {
   3:     var points = new List<GISPoint>();
   4:  
   5:     foreach (SPListItem listItem in web.Lists[listName].Items)
   6:     {
   7:             points.Add(new GISPoint(
   8:                 listItem["title"], 
   9:                 listItem["address"], 
  10:                 listItem["email addess"], 
  11:                 listItem["site Url"], 
  12:                 listItem["telephone"], 
  13:                 listItem["fax"], 
  14:                 listItem["latitude"], 
  15:                 listItem["longitude"]));
  16:     }
  17:     return points;
  18: }
  19:  
  20: private List<ZoomArea> LoadZoomAreasFromSharepoint(SPWeb web, string listName)
  21: {
  22:          var points = new List<ZoomArea>();
  23:  
  24:          foreach (SPListItem listItem in web.Lists[listName].Items)
  25:          {
  26:            points.Add(new ZoomArea(
  27:                 listItem["title"],
  28:                 listItem["latitude"], 
  29:                 listItem["longitude"], 
  30:                 listItem["radius"], 
  31:                 listItem["zoom"]));
  32:          }
  33:          return points;
  34: }
  35:   

I then used Isolator to intercept the calls to these methods, this can be done by using the Members.CallOriginal flag to wrapper the actual class and intercept the calls to the private methods. Note that I am using different helper methods to create the list of my own data objects as opposed to List<SPListItems>

   1: var controlWrapper = Isolate.Fake.Instance<LocationMap>(Members.CallOriginal);
   2: Isolate.Swap.NextInstance<LocationMap>().With(controlWrapper);
   3:  
   4: Isolate.NonPublic.WhenCalled(controlWrapper, "LoadFixedMarkersFromSharepoint").WillReturn(CreateCentreListAsGISPoint());
   5: Isolate.NonPublic.WhenCalled(controlWrapper, "LoadZoomAreasFromSharepoint").WillReturn(CreateZoomAreaListAsZoomItems());
   6:   

My workaround, in my opinion, is a weaker test as I am not testing my conversion of SPListItems to my internal data types, but at least it works

I have had to go down this route due to a bug in Typemock Isolator (which has been logged and recreated by Typemock, so I am sure we can expect a fix soon). However it does show how powerful Isolator can be when you have restrictions in the changes you can make to a code base.Wrapping a class with Isolator can upon up a whole range of options.

I am speaking at the VBug Autumn Conference

The VBug Autumn Conference is on the Wed 27th & Thurs 28th October  at Holywell Park, Loughborough. The published agenda this far is

Day One (Wed 27th Oct):
Top of the Pops with Sharepoint 2010 – Dave McMahon
Cache Out with Windows Server AppFabric – Phil Pursglove
Mapping the Cloud – How far can we stretch it? Johannes Kebeck
Silverlight Development on Windows Phone 7 – Andy Wigley

Day Two (Thurs 28th Oct):
TFS (actual title TBC) – Richard Fennell
Silverlight 4 (actual title TBC) – Richard Costall
Others to be confirmed…including Azure, Expression Blend & BI

Well I can give a bit more detail on what I will be talking one, it will be a TFS session focused on the the benefits for people moving from Visual SourceSafe, so work item tracking automated build etc.

Hope to see you there