Test your C# or F# Library on Mono with Vagrant

Most .Net libraries should also work on Linux or OS X thanks to Mono. But, do they really? How do you verify and test that, without installing Mono on your Windows development box, or setting up a separate Linux box? Or how to compile and test some local files without installing any .net or mono dev tools at all?

Vagrant comes in handy here. Vagrant may not be very well known among .Net developers on Windows yet, so let the Vagrant team introduce it in their own words: "Vagrant is a tool for building complete development environments. With an easy-to-use workflow and focus on automation, Vagrant lowers development environment setup time, increases development/production parity, and makes the "works on my machine" excuse a relic of the past." In my words, Vagrant lets you define a standardized development or test environment for your project that works exactly the same everywhere, no matter what OS you're on or how you've set it up. It uses virtual machines in the background, but you neither see nor care about them much.

Using Mono with Vagrant

The Math.NET Numerics project claims that it supports Mono, but I admit I verify that claim sporadically only. Up to now, that is. I've just enabled Vagrant on the project, so everyone can test it on Mono without any effort. This is what you do, assuming you have Vagrant installed and you have a local checkout of the repository: open git bash at the root of the checkout and run

1: 
$ vagrant up

This will download the box the first time it is used (~540 MB), create a virtual machine in VirtualBox and then start and provision it. After the first time this is quite fast, usually less than a minute.

Vagrant Up

Then we can enter the environment. We see that both mono and fsharp are available and up to date:

1: 
$ vagrant ssh

Mono

FSharp Interactive

The trick is that the local directory is automatically available within the environment in the /vagrant path. We can compile our project using mono and xbuild right away:

1: 
2: 
3: 
4: 
5: 
~$ xbuild /vagrant/MathNet.Numerics.sln
# or just some projects:
~$ xbuild /vagrant/src/Numerics/Numerics.csproj
~$ xbuild /vagrant/src/FSharp/FSharp.fsproj
~$ xbuild /vagrant/src/UnitTests/UnitTests.csproj

And then run all the unit tests using NUnit:

1: 
2: 
~$ nunit-console /vagrant/out/tests/Net40/MathNet.Numerics.UnitTests.dll
~$ nunit-console /vagrant/out/tests/Net40/MathNet.Numerics.FSharp.UnitTests.dll

NUnit

And indeed, 37 of 11906 tests are failing - apparently there are some differences around number formatting. Seems like we've got some work to do. In order to fix it, I open Visual Studio on Windows as usual, compile it there (or in the Vagrant environment using xbuild), and run the tests again both in Visual Studio using .Net and in the environment using Mono. No manual file transfer or copying is needed.

If you're done working for now, leave the environment with exit and either suspend (vagrant suspend) or shutdown (vagrant halt) the virtual machine. In either case, you can bring it up again later with vagrant up, or remove it completely with vagrant destroy.

Installing Vagrant

I mentioned before that you need to have Vagrant installed for this to work:

  • Download and install Vagrant from here
  • Download and install VirtualBox from here
  • Not sure this is still needed, but to be on the safe side add the VirtualBox folder to your system PATH environment variable. In my case the folder is "C:\Program Files\Oracle\VirtualBox".

If you need more help there, have a look at Vagrant's getting started guide.

Enabling Vagrant in your own project

There are really only two things I did to enable this in Math.NET Numerics:

  1. Add ".vagrant" to the .gitignore file (or the equivalent if you don't use git).
  2. Add a Vagrantfile text file to the repository

The Vagrantfile can be generated automatically with the vagrant init command, but it may even be easier to just start from the file I currently use:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
Vagrant.configure("2") do |config|

  config.vm.box = "wheezy64-mono3.0.10-fsharp3.0.27"
  config.vm.box_url = "https://dl.dropboxusercontent.com/s/uelesklqouaw1gl/wheezy64-mono3.0.10-fsharp3.0.27-virtualbox.box"

  config.vm.provider :virtualbox do |vb|
  vb.gui = false
  vb.customize ["modifyvm", :id, "--memory", "1024"]
  vb.customize ["modifyvm", :id, "--cpus", "2"]
  end
end

The file declares the base box to use and where it can be downloaded from if needed. This specific box by default uses only 384 MB RAM and 1 CPU, but we override this in our project to give it some more resources. You could also declare more folders to be kept in sync or network ports to be forwarded in this file.

This file is also the right place to specify additional provisioning if needed. How this works is described in the Vagrant docs. Note however that this specific box does not have ruby installed and is not prepared to be provisioned with tools like puppet or chef. Simple provisioning by shell script should work though, and good enough to install some more packages or change some settings if needed.

About the Base Box

Ideally we could just use an existing official Debian base box and provision mono and fsharp using Vagrant's provisioning mechanisms. Hopefully we'll get there in the end, e.g. by giving the Debian package maintainers a hand. Unfortunately for now the Debian packages are out of date (but they actually exist which is good news) so we need to compile both mono and fsharp from sources. Compiling them takes some time on a VM and seems inappropriate to me to do at provisioning time. So I've created a new VM from scratch where I can compile the tools locally and extract cleaned-up Vagrant base boxes in a relatively straight forward way, whenever a new version is released.

Current Specs:

  • OS: Debian 7 "Wheezy", 3.2.0-4-amd64 Linux Kernel (3.2.41-2+deb7u2)
  • VM: VirtualBox, Guest Tools for v4.2.12 (installed from VB instead of using the Debian packages)
  • Size: 542 MB when packed
  • Defaults to 384 MB Ram, 1 CPU. Dynamically expanding disk, max 40 GB.
  • Mono 3.0.10 and F# 3.0.27 compiled locally from tagged sources, installed to /usr
  • NUnit 2.6.1 official binary added manually to /usr/local, hence effectively hiding nunit-console from Mono in /usr/bin but not Mono's nunit-console2 or nunit-console4. Note that Mono comes with NUnit 2.4.8.

I've made the box available on Dropbox for now until we find a better place. To use it either copy the Vagrantfile above, or add the box manually using something like:

1: 
$ vagrant box add wheezy64monofs https://dl.dropboxusercontent.com/s/uelesklqouaw1gl/wheezy64-mono3.0.10-fsharp3.0.27-virtualbox.box

Known Issues

  • The F# tools seem to clear the shell at start which is a bit irritating. Might be related to git bash, but if I remember right this did not happen back when I used the Debian-provided VirtualBox Guest Tools. Considering going back to them.
  • I did not manage to fully turn off USB in VirtualBox; apparently it is needed by some HID device (maybe the mouse?).

I'm no expert on Vagrant or even on using Mono and F# on Linux myself, so any feedback and suggestions for improvements are very welcome. Let me know if this works for you or whether we need to go further.

Of course there are also ways to get F# and/or mono straight to your box without the need for Vagrant, see the F# Software Foundation website for instructions on how to get started on your platform.