Coloured output from Erlang EUnit tests using Rake

I’m spending more and more time fiddling around with Erlang lately, and I’ve recently started using EUnit to write unit tests for my code. As I spend my working days writing Ruby code, I’ve found using Rake files to compile and run my code a lot easier to deal with than scary Makefiles.

The Rakefile I’ve been using to compile my code is from Building Erlang with Rake , and whilst working on a Rake task to run my unit tests I came across Using Rake for Erlang Unit Testing . Based on this, I’ve written a rake task to run my EUnit tests and give me the coloured output I’m used to from Ruby unit testing frameworks such a Rspec.

Here's the Rakefile:

require 'rake/clean'

INCLUDE = "include"
ERLC_FLAGS = "-I#{INCLUDE} +warn_unused_vars +warn_unused_import"

SRC = FileList['src/*.erl']
OBJ = SRC.pathmap("%{src,ebin}X.beam")
CLEAN.include("ebin/*.beam")

directory 'ebin'

rule ".beam" => ["%{ebin,src}X.erl"] do |t|
  sh "erlc -pa ebin -W #{ERLC_FLAGS} -o ebin #{t.source}"
end

desc "Compiles all .erl files in src to .beam files in ebin"
task :compile => ['ebin'] + OBJ
task :default => :compile

desc "Compiles all .erl files in src to .beam files in ebin"
task :compile_with_tests do
  FileList['src/**/*.erl'].each do |src|
    sh "erlc -D EUNIT -pa ebin -W #{ERLC_FLAGS} -o ebin #{src}"
  end
end

desc "Run all tests"
task :test => :compile_with_tests do
  modules = OBJ.map {|o| File.basename(o, ".beam") }
  output = `erl \
  -noshell \
  -pa ebin \
  -eval 'eunit:test([#{modules.join(",")}], [verbose])' \
  -s init stop`

  output.each_line do |line|
    case line
    when /= (EUnit) =/
      print line.gsub($1, green($1))
    when /\*failed\*/
      print red(line)
    when /(\.\.\..*ok)/
      print line.gsub($1,green($1))
    when /Failed:\s+(\d+)\.\s+Skipped:\s+(\d+)\.\s+Passed:\s+(\d+)\./
      puts "#{red("Failed: #{$1}")} Skipped: #{$2} #{green("Passed: #{$3}")}"
    when/(All \d+ tests passed.)/
      print green(line)
    else
      print line
    end
  end
end

def green(text)
  "\e[32m#{text}\e[0m"
end

def red(text)
  "\e[31m#{text}\e[0m"
end

The :compile_with_tests task will, as expected, compile all the *.erl source files in the src/ directory with the -D EUNIT flag which will compile the tests in. The :test task will run the tests and give simple coloured output. We can test the task using the following simple Erlang module:

-module(utils).
-compile(export_all).

-ifdef(EUNIT).
-include_lib("eunit/include/eunit.hrl").
-endif.

double(X) ->
  X * 2.

triple(X) ->
  X * 3.

% Tests

-ifdef(EUNIT).

double_test_() ->
  {inparallel,
    [
      ?_assertEqual( 100, utils:double(50)),
      ?_assertEqual( 50, utils:double(20))
    ]
  }.

triple_test_() ->
  {inparallel,
    [
      ?_assertEqual( 9, utils:triple(3)),
      ?_assertEqual( 75, utils:triple(25))
    ]
  }.

-endif.

Running rake test produces the following:

barry@pinga:~/code/erlang$rake test
(in /Users/barry/code/erlang)
erlc -D EUNIT -pa ebin -W -Iinclude +warn_unused_vars +warn_unused_import -o ebin src/utils.erl
======================== EUnit ========================
module 'utils'
  utils:21: double_test_...ok
  utils:22: double_test_...*failed*
::error:{assertEqual_failed,[{module,utils},
                           {line,22},
                           {expression,"utils : double ( 20 )"},
                           {expected,50},
                           {value,40}]}
  in function utils:'-double_test_/0-fun-2-'/1

  utils:29: triple_test_...ok
  utils:30: triple_test_...ok
  [done in 0.005 s]
=======================================================
Failed: 1 Skipped: 0 Passed: 3

Fixing this error and rerunning the rake task outputs:

barry@pinga:~/code/erlang$rake test
(in /Users/barry/code/erlang)
erlc -D EUNIT -pa ebin -W -Iinclude +warn_unused_vars +warn_unused_import -o ebin src/utils.erl
======================== EUnit ========================
module 'utils'
  utils:21: double_test_...ok
  utils:22: double_test_...ok
  utils:29: triple_test_...ok
  utils:30: triple_test_...ok
  [done in 0.005 s]
=======================================================
  All 4 tests passed.

One issue with this is that the :compile_with_tests task will always recompile source files regardless of whether they need recompiling or not, the standard :compile task only compiles when the source files have been modified. Other than that, this should work as intended. I'm sure someone will now tell me that this functionality is available in EUnit as it is, but I did Google for this, I promise.


iTunes smart playlists

Despite exhibiting a fair amount of autistic behaviour towards most aspects of my music listening (I’ve devoted numerous evenings to tagging, renaming, and artworking pretty much all my mp3s), I am pretty slack in rating songs in iTunes. Which is a shame, because having all my songs rated would help me massively when making playlists. Luckily, due both to a nerdy desire to automate most things and lots of laziness, I’ve got a few smart playlists which try and do that job for me.

Songs I love, but haven’t listened to for a month.

Loved songs being those I’ve listened to more 5 times. Over time I’ll increase the number, but at the moment 5 is working out pretty well for me.

Songs I like, but haven’t listened to for a month.

Same as the loved list, but only 2 plays are required to be included. Also, excluding the loved list so there are no duplicates.

Songs I really love

So I’ve listened to them loads, and never skipped them - I must really love these ones. By the way, as far as I know, Apple’s definition of skipping a song is when you press next any time between the 2nd and 20th second of a song. I wish it didn’t have the 2 second buffer at the start, I usually skip songs within a second of hearing one I can’t be bothered with.

10 songs from last week

Not sure why I like this playlist, but I do. It’s quite nice to hear a song again after a few days.

OK, so those are the main playlists I use on my iPod. I don’t sync directly with them though, I have a folder which contains the lists I sync with.

10 from last week is the one I mentioned above, Genius is the Apple Genius playlist I currently have, Liked/Loved 3gb is

just a mix of the liked/loved playlists limited to 3GB.

Played, random is random songs I’ve listened to at least once, but not in the past week

The unplayed list, is the same as the played, but with a play count of 0.

The final playlist

It’s only now I’ve written it all down that I realise how convoluted and complicated this all is, but from this folder comes the final playlist. I only sync with this one.

It’s the iPod Smart folder, excluding all the songs I skipped last week, because chances are I still don’t want to listen to them. It’s limited to 10GB, because this is for my iPhone, I have this same playlist for my 4GB Nano, but limited to 4GB (obv). It’s quite nice being able to do it this way, it means I don’t have shuffle around the sizes of all the playlists I use.

And that’s pretty much it. There are some limitations with the smart playlists - I’d love to be able to have a playlist based on songs I’d listened to the most times in the last week/month/year, and I wish I could make it pick a random album for playlists. Unless I missed something. These things are possible with all my last.fm data , I just need a way to get that data into iTunes. That can be another side project for me.


Scenes - a Rails plugin

We're big fans of Cucumber at work - we're using it on all new projects we start, and it's quickly becoming as essential to us as writing tests using Rspec. We first started using it last year on a reasonably complicated Rails app, and we had a lot of features which required the system to be in a certain state before we could test what the feature was actually about. There are different ways to do this now - e.g. background steps - but as a solution to this problem I wrote what eventually became scenes.

It's probably easiest to demonstrate with a simple example.

Scenes::Character.named("David") {
  User.create(:name => "David")
}

Scenes::Character.named("Joe") {
  User.create(:name => "Joe")
}

Scenes::Scene.named("David and Joe") {
  Scenes::Character["David"]
  Scenes::Character["Joe"]
}

This will set up a scene containing two characters - David and Joe. In the Rails console, we can now load this scene and then play it back to create these users.

Loading development environment (Rails 2.3.2)
>Scenes::load
=> ["scenes/scenes.rb"]
>Scenes::Scene.list
=> ["David and Joe"]

Scenes::load will read all ruby files in the scenes/ directory. Scenes::Scene.list lists the scenes that have been loaded.

>User.count
=> 0
>Scenes::Scene["David and Joe"].play
=> true
>User.all.map(&:name)
=> ["David", "Joe"]

Playing this scene back created David and Joe. The first time a character is referenced, the object is created in the database. Subsequent references will load this object rather than duplicating it. Characters can contain any additional code for setting up the character (eg, activating a user account), but must return the object they are creating.

Scenes::Character.named("David") {
  u = User.create(:name => "David")
  u.activate!
  u
}

Usage with Cucumber

To play scenes in a cucumber scenario, you first have to load the scenes, and then create a step which will load them.

Scenes::load

Given /^the scene "(.+)"$/i do |scene_name|
  Scenes::Scene[scene_name].play
end

I put this code in my features/support/env.rb, although the step definition should probably live in its own definition file. Once this code is in place you can use a scene in a scenario as follows

Given the scene "David and Joe"
When I am on the users page
I should see "2" users

As mentioned at the beginning of this post, this is a very simple example, but it should show how to use scenes.

Rake tasks

There are also two rake tasks for scenes, the first will list all the scenes you have set up and allow you to load them in to your development environment.

$ rake scenes
(in /Users/barry/code/ruby/example)

Available Scenes
----------------

   1 - David and Joe

This will REBUILD the database first

Or 0/q to quit
> 1
==  CreateUsers: migrating ================================
-- create_table(:users)
   -> 0.0019s
==  CreateUsers: migrated (0.0027s) =======================

This task will run rake db:migrate:reset to clear your development database, then play the selected scene. This is handy for checking that you have set your scene up correctly, and it's been a useful way of setting apps up in a predetermined state for weekly demos. The other task allows you to save your current development environment as a scene for future playback.

$ rake scenes:save
(in /Users/barry/code/ruby/example)

Enter a name for this scene:
> my awesome scene
Saved
$ ls scenes/saved/
my_awesome_scene.rb	my_awesome_scene.yml

This saves your data in a yaml file along with a ruby file which sets the scene. This task is quite simple, and if you have any database constraints in place it probably won't work as expected.

Hopefully, that's given a basic example of using this plugin. It's still a work in progress, and the code is up on github.