A place for spare thoughts

21/02/2012

MSTest 2010 without Visual Studio at build server – again

Filed under: MSBuild, Unit testing — Ivan Danilov @ 02:53

A while back I wrote on that topic. Today I found that that solution was not working when MSTest private accessors were used in the solution.

In general I think that these ability to access private members of the class encourages bad design and nullifies one of the most useful features of testing: necessity to think about how your class can be used and how convenient its interface is. If you have ability to work with privates – you don’t have to think. You could just write code as it goes. How are consumers of the class supposed to use it when even its author can’t?!
Thus, IMO, ability to generate private accessor to a class is one of the most abominable things in testing and I’ll never ever use it.

But unfortunately others do sometimes. If you have such a project and try to build it on a machine that was setup using my previous advice – you’ll see something about compiler can’t find out meaning of name YourClass_Accessor. Of course it can’t – it wasn’t generated yet! To overcome this issue you have to copy in addition to previous files also this folder:

c:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v10.0\TeamTest\

It contains *.targets file that was foresightfully imported even in default Microsoft.Common.Targets (even if you will never have MSTest – you still have line to import it if it is found), one assembly with custom MSBuild Task implementation and some exe-module. For some reason, MSTest developers can’t manage to write a product without such hacks at every corner. Whatever.

So, you just copied these files and msbuild will find them. Do not even hope to have such a luxury for your products 🙂
But when I rerun the build – I found such error:

C:\Program Files\MSBuild\Microsoft\VisualStudio\v10.0\TeamTest\Microsoft.TeamTest.targets(14,5): error : Could not load file or assembly ‘file:///C:\work\TestSolution\TestProject\Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll’ or one of its dependencies. The system cannot find the file specified.

What?! Why the hell MSTest tries to load an assembly from current directory and not with normal binding mechanism? I have this assembly on my build machine and it actually can be loaded when referenced from project in normal way. I didn’t found answer to that question as well as option to alter this probe somehow. But with fusionlog I found that just after probe to current dir it tries to load same assembly from its default location at c:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\PublicAssemblies\Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll. Ok. Let’s stick to it. And I’ve copied all of my assemblies not to c:\vs2010stub as I suggested in previous article, but to default c:\Program Files (x86)\Microsoft Visual Studio 10.0\ folder. And it worked – private accessors got generated and publicized correctly.

So I made related changes to my archive and re-uploaded it to old location. Just unpack somewhere on build server and run install.bat. Should work afterwards.

25/07/2011

Checking solutions and project files against some set of rules at build-time

Filed under: MSBuild, Unit testing, VisualStudio — Ivan Danilov @ 04:23

Currently I’m busy with multi-solution project. There’re several teams on the project and each team works on one or several solutions. Sometimes together, sometimes not. There are network of dependencies between solutions (e.g. some shared library that is produced as build artifact of one solution and used by two other).

Clearly that we have some set of rules how things should be organized. And this set at some stage became complex enough to be almost unmaintainable manually.

For example, each project should contain property named OutputRoot defined relative to $(SolutionDir) and OutputPath should be relative to it. Thus we achieve that every artifact is placed to some well-known place under OutputRoot which could be redefined from msbuild command line.

Or the rule that requires HintPaths properties for references should be relative to that $(OutputRoot) or $(SolutionDir). So that no one could place absolute path there that will work only in his environment. Well, it could work on others machines as well by some lucky accident. But build robustness is not that sort of things we want to have by accident.

Also there’s some general rules:

  • every C# project (*.csproj file) should belong to the solution and only one;
  • solutions can’t be nested so if some folder has *.sln file – entire tree from this point should have to other *.slns;
  • ProjectReference couldn’t point outside of solution directory or have absolute path;
  • every project should have property TreatWarningsAsErrors with value true;
  • if Deployment project exists in solution – every other project should have reference to it;
  • test projects (we are using convenience that test projects should be named like *.Tests) are marked with corresponding guids so that test runner can run them;
  • etc…

And so the idea of having some kind of source checker was born. In fact, *.sln and *.csproj are not very complex formats to parse. And we’re not going to support every possible thing in them. We want only to read and check some set of rules.

This source checker now runs on each commit in the source control from the build server and tests every rule we decided to check. If some rule was violated – it reports that, fails build and suggests what should you do to fix the thing.

It is able also to ignore some directories (for example it ignores itself because it shouldn’t be built into $(OutputRoot)). Other useful thing is that it could be integrated with TeamCity so that build log looks pretty there (you should specify /teamcity key in the command line).

Also rules could be easily added or removed.

It proved to be very useful in our day-to-day work and so I’d like to share them with everybody.

It could be not production quality sometimes and several hard-coded things could be inside but nevertheless it is still usable.

Sources could be found here. It is targeting .NET 4.0, but currently it is almost trivial to retarget it against .NET 3.5, so if you’re still there – it is not a problem.

20/07/2011

Deployment as part of development build

Filed under: MSBuild, VisualStudio — Ivan Danilov @ 19:25

Last two years or so I’ve been mostly developing solutions that are plugins to some other systems. Sometimes it is third-party library, sometimes it is some kind of corporate-standard-framework with some common presentation functionality whatever.

The shared feature of every such project is that is should be installed in some way into that external system. Most often installing is just copying DLLs into some predefined folder for plugins. Sometimes it is also required to register something in registry, settings of the system or somewhere else. And that is the problem. When you’re working from Visual Studio – the most natural process is to make changes and hit F5 to run and see your changes live (I don’t take testing into consideration here but it is an important part of course). Well, VS can run external application under debugger. But who will make installation steps?

I’ve seen several approaches there. Some teams just point OutputPath to external system’s plugins folder so that building takes place there. It is not a good idea as csproj files are under source version control system and paths could be different on different machines. Most obvious example is that when external system is 32bit – it has different paths on 32bit and 64bit windows. Other example is build server that could even not having this external system installed. Its task usually is to build (and probably run unit tests, code coverage, make installation kit etc), not to run actual system. Moreover if external system is running and have your assemblies from previous build locked (as they’re loaded into it) – VS can’t clean them or replace with new ones which sometimes lead to confusion.

Yet another teams perform build with default settings and then manually (or with help of some batch file) copying needed artifacts to the place where they should be. It is slightly better in terms of build server integration and locked files. But it requires three steps to run application: build solution from VS, switch to explorer (or whatever you’re using) and run batch file, switch back to VS and run external system with debugger attached. It is not only a pain, it is also error-prone as one could easily forget to do one of these steps.

And yet another teams have post-build step in some project. And that post-build step executes actual install. Here we have problem with project build skipping. VS has some heuristics to determine which projects should be built and which could be skipped. First of all it skips everything that is up-to-date (i.e. hasn’t any file or referenced project changed after last successful build). Also VS skips everything that wasn’t set up for build in current configuration (in the solution properties). Thus said if project build is skipped – post-build step is skipped also. Oops. VS doesn’t know that you have some dependencies in the post-build step. And worse it would be skipped without any warning so that you can find it only when something goes wrong. Not good definitely.

The solution to this problem that I came with in the end is to have separate project in the solution dedicated to this installation step. And this solution should have references to any other project in the solution. To be clear here, this Deployment project is not going to call compiler or produce build errors if referenced projects were not built (due to current configuration for example). The single purpose of these references is to give VS knowledge about when it could really skip build of Deployment project – only when no projects in the solution changed. Otherwise it should build Deployment.

The downside of this approach is evident: you should carefully watch over rule that Deployment has these references actual. But this task could be automated more or less easily. Maybe I will write a blog post soon about such thing. We have this checking as a part of build process so if anyone makes a change violating the rules we have failed build. It just works.

UPDATE: I’ve written such post. See here.

And here is example of Deployment.csproj file:

<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
  <PropertyGroup>
    <ProjectGuid>{5739A0D8-2EE2-4CEE-BFCD-C748E47E320D}</ProjectGuid>
    <OutputType>Library</OutputType>
    <!-- VS2010 for some reason requires OutputType property set. Otherwise it throws
         an error when you try to open project properties window -->
  </PropertyGroup>
  <ItemGroup>
    <ProjectReference Include="..\YourProject\YourProject.csproj">
      <Project>{3612C9DD-7086-436A-9AB7-A3EAA995881F}</Project>
      <Name>YourProject</Name>
    </ProjectReference>
    <!-- Other project references goes there -->
  </ItemGroup>
  <Target Name="Build">
    <ItemGroup>
      <ToCopy Include="..\YourProject\bin\Debug\*.dll" />
      <ToCopy Include="..\YourProject\bin\Debug\*.pdb" />
      <ToCopy Include="..\YourProject\bin\Debug\*.config" />
      <ToCopy Include="..\AnotherDirToCopyFrom\*.*" />
      <!-- Actually you could have any set of things to copy here -->
    </ItemGroup>
    <Message Text="ToCopy = $(ToCopy)" />
    <Message Text="DestinationFolder = $(InstallDir)\Plugins\YourPluginName" />
    <Copy Condition=" '$(InstallDir)' != '') " 
          SourceFiles="@(ToCopy)" 
          DestinationFolder="$(InstallDir)\Plugins\YourPluginName" />
    <!-- This target copy things only if %InstallDir% environment variable exists.
         It prevents copying from executing on build server if it doesn't have the system
         installed.
         You could have every conditional logic you want instead. -->
  </Target>
  <Target Name="Clean" />
  <!-- If you need to do something on the Clean - put it here. But don't delete
       this target at all because VS will give you an error that project does not have
       Clean target on each Clean or Rebuild -->
  <Target Name="Rebuild" DependsOnTargets="Build" />
</Project>

The main point here is that we don’t import $(MSBuildToolsPath)\Microsoft.CSharp.targets and define our own Build, Clean and Rebuild targets which are called by VS. Probably you will have to define also Publish target if you use it.

Blog at WordPress.com.