Two steps for faster browser specs

March 11, 2013Posted by Tom Meier

Keeping specs fast is a never ending battle for the average Rails developer. There are 2 quick lines of javascript that can make excellent and stable improvements to spec speed performance.

Most people are aware of JQuery giving the option to disable animations (see – fx.off), we can disable this in test mode making all browser based specs faster by skipping JS animation. This is acceptable, as we know it works, it’s still being triggered but skips the time-consuming part for our specs.

The same can be said for CSS transformations; sliding a menu down and hiding a div for example. We know they work, but don’t want our specs held up by the animation.

To acheive this, simply apply the following code to your Rails app :

app/views/layouts/_spec_performance.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<% if Rails.env.test? -%>
  <%= content_for :head do %>
    <style type="text/css">
      .notransition * {
        /*CSS transitions*/
        -webkit-transition: none !important;
        -moz-transition: none !important;
        -o-transition: none !important;
        -ms-transition: none !important;
        transition: none !important;
        /*CSS transition properties*/
        -webkit-transition-property: none !important;
        -moz-transition-property: none !important;
        -o-transition-property: none !important;
        -ms-transition-property: none !important;
        transition-property: none !important;
        /*CSS transforms*/
        -webkit-transform: none !important;
        -moz-transform: none !important;
        -o-transform: none !important;
        -ms-transform: none !important;
        transform: none !important;
        /*CSS animations*/
        -webkit-animation: none !important;
        -moz-animation: none !important;
        -o-animation: none !important;
        -ms-animation: none !important;
        animation: none !important;
    }
    </style>
  <% end %>

  <script type="text/javascript">
    $.fx.off = true;
    $('body').addClass('notransition');
  </script>
<% end -%>
app/views/layouts/any_layout.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
<html>
  <head>
  <%= yield :head %>
  </head>
  <body>
  ...
  <%= yield %>
  ...
  <%= render partial: "layouts/spec_performance" %>
  </body>
</html>

Thats it. I’m seeing ~31% speed improvements on our mobile specs, and ~18% speed improvements on our normal Capybara features.

Tagged ruby, rails, programming, rspec, testing, capybara, selenium


32 bits of Ruby

April 11, 2011Posted by Tom Meier

A recent rabbit hole that I fell in, related to a rather obscure Ruby 32bit → 64 bit Time parsing issue. I’ll lay it all out here in the hope that no-one else falls into it.

As per usual, all the code was working swimmingly on my own machine (macbook pro – 64 bit with the trimmings, hold the fat), but the minute it was deploying onto multiple servers and the testing environment (see – Jenkins or whatever continuos integration philosophy you employ), a few inserts into the database were chucking nasty “ArgumentError: time out of range” errors.

First picked up when attempting to load fixtures for the test suite. With a bit of hunting this narrowed down to simply one of the date values being set at ‘3000-01-01’ (legacy systems groan), as you can see here:

my 64 bit machine:
1
2
3
4
>> Time.parse('3000-01-01')
=> Wed Jan 01 00:00:00 +1100 3000
>> DateTime.parse('3000-01-01')
=> Wed, 01 Jan 3000 00:00:00 +0000
on 32 bit testing server:
1
2
3
4
5
6
7
8
ree-1.8.7-2011.03 :001 > Time.parse('3000-01-01')
ArgumentError: time out of range
        from /usr/local/rvm/rubies/ree-1.8.7-2011.03/lib/ruby/1.8/time.rb:184:in `local'
        from /usr/local/rvm/rubies/ree-1.8.7-2011.03/lib/ruby/1.8/time.rb:184:in `make_time'
        from /usr/local/rvm/rubies/ree-1.8.7-2011.03/lib/ruby/1.8/time.rb:243:in `parse'
        from (irb):1
ree-1.8.7-2011.03 :002 > DateTime.parse('3000-01-01')
 => Wed, 01 Jan 3000 00:00:00 +0000

So the long and short of it is that Ruby in a 32 bit environment, when running Time.parse tends to have a ‘bit’ of a hissy fit with any years greater than 2038. Time has a limited range of 1901 – 2038, anything, and I mean anything, even a second outside this range, requires DateTime to be used instead.

With my particular issue, I had to track down where it was calling Time.parse, and why the festicky was it not using a superior DateTime.parse call on the input data. Long live the testing suite! A quick change added to the spec_helper.rb:

spec/spec_helper.rb
1
2
3
4
5
6
  #Raise on the method call - REMOVEME
  class Time
    def self.parse(date, now=self.now)
      raise "THIS LITTLE BUGGER CALLED ME : #{caller.inspect}"
    end
  end

On the next run of the relevant spec, which loaded the problematic date times, I could then see the cause of my current despair was none other than Sequel . Sequel has been a fantastic utility, and I’m preferring it over Arel and ActiveRecord for the extra control it gives me. However, this was the root cause.

Turns out Sequel is well aware of the issue and had an immediate fix to hand already, boomshanka!

http://sequel.rubyforge.org/rdoc/classes/Sequel.html

datetime_class [RW] Sequel can use either Time or DateTime for times returned from the database. It defaults to Time. To change it to DateTime:

Sequel.datetime_class = DateTime

For ruby versions less than 1.9.2, Time has a limited range (1901 to 2038), so if you use datetimes out of that range, you need to switch to DateTime. Also, before 1.9.2, Time can only handle local and UTC times, not other timezones. Note that Time and DateTime objects have a different API, and in cases where they implement the same methods, they often implement them differently (e.g. + using seconds on Time and days on DateTime).

Job done. Setting the Sequel default to use DateTime instead of time resolves this issue for both 32 bit and 64 bit machines, and will work on any modern ruby version.

Tagged ruby, rails, programming


Static content in Rails 3

January 30, 2011Posted by Tom Meier

To auto-generate static pages in Rails 3, other than by simply adding html files to the ‘public’ directory, is very easy.

The most simple way is to just; add a controller such as ‘PagesController’, redirect all unmatched routes there, create a view and it will be displayed if the html/haml view exists – with any others raising a template not found error (display 500 to the production user).

However, I usually want to capture whats going on behind the scenes, or share variables across a group of pages.

Disclaimer – this isn’t the ideal or the best way, but it is my convenient way, let me know if you have any tips ’n tricks to improve it further!

Static Schoolgirls

My usual setup can be handy, if you want custom static pages for both scoped areas of those logged in and those not, or even subdomains, anything is possible really with the Rails 3 routing setup. Simply add the following:

Create a pages controller class, under any submodule or folder structure you like.
This will simply check if the view template exists (even haml) and render that, otherwise render out your custom 404 page, the only params being sent are ‘base_page’ which will be set by the routes.

app/controllers/pages_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class PagesController < ApplicationController

  def base_page_processor
    view_prefix = "pages"

    if params[:base_page].present? && template_exists?(params[:base_page], view_prefix)
      render "#{view_prefix}/#{params[:base_page]}"
    else
      #TODO : Notify missing url via email error or error notification service
      render '/public/404.html', :status => 404
    end
  end

end

To ensure all routes are matched, that aren’t specifically set in the config/routes.rb file, add this line right at the very end of the file (or at the end of a specific scope, such as a logged in section) :

config/routes.rb
1
match  ':base_page', :controller => 'pages', :action => 'base_page_processor', :as => :page_processor

Some specs to help you along the way (using RSpec), and ensure you don’t break the setup as time goes on:

spec/controllers/pages_controller_spec.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
require 'spec_helper'

describe PagesController, 'automatic paths' do
  before(:all) do
    @routes = Rails.application.routes
  end

  it "should route all traffic and render if template exists" do
    ["/some_random_path", "/something_else", "/doesnt_exist"].each do |dynamic_path|
      @routes.recognize_path(dynamic_path).should == {:controller => 'pages', :action => 'base_page_processor', :base_page => dynamic_path.gsub(/^\//,'')}
    end
  end

  it 'should not route any traffic with sub paths' do
    ["/some_random_path/with_sub/lines", "/something_else/123/something.csv", "/doesnt_exist/nope"].each do |dynamic_path|
      lambda { @routes.recognize_path(dynamic_path) }.should raise_error(ActionController::RoutingError)
    end
  end

  describe 'with templates existing' do

    it 'should render the template' do
      controller.stub!(:template_exists?).and_return(true)
      controller.stub!(:render)

      controller.should_receive(:render).with( 'pages/something_that_exists' )

      get 'base_page_processor', :base_page => 'something_that_exists'

      response.status.should == 200
    end
  end

  describe 'when template doesnt exist' do

    it 'should render the 404 page' do
      get 'base_page_processor', :base_page => 'something_that_doesnt_exist'

      response.status.should == 404
      response.should render_template('public/404')
    end
  end

end

Now simply add any view template in your set page prefix, in the above example this would be ‘/app/views/pages/any_template_file’, this will now immediately load in your app. Easy.

Tagged ruby, rails, programming