September 09, 2006

Patching Code is Easy in Ruby

A nice thing about open-source software is that you can change the code yourself if necessary. If you want to fix a bug or add a feature you can make the change yourself. You don’t need to get permission from any one.

But how can you share your change with others? If a standard distribution doesn’t accept your changes, you can send the patch to other users directly. But most won’t use them; most wouldn’t know how. And that is because most patches are structured diff files identifying what lines of what files were changed and how. Unless users themselves have a development environment, they won’t know how to make use of this conventional type of patch.

But Ruby provides a unique method for patching code that makes life easier for both the programmer creating the patch and the user who wants to use it.

I’ll describe this method in detail in a moment. As lead developer for Watir I often get submissions of enhancements. Most frequently these arrive as a set of modified Ruby libraries, which are hard to review. I can’t easily browse through a 4,000 line file of Ruby code to figure out which lines have been changed or added. To consider it, I essentially have to create a conventional patch file myself. At the end of this article, I’ll describe how to do this. This process is well known to most open-source developers (of any language), but many Watir users are not developers and are new to open-source.

But even so, I often won’t accept these changes no matter how they are delivered: I’m a stickler for unit tests, many of the submissions will break existing scripts, and I’m reluctant to add new features that are still half-baked (because users will start using them, and then resist us when we are ready to finish baking them later if they will require them to make changes to their scripts). These suggestions are usually good, useful ideas that will die on the vine unless me or one of the few other Watir commiters do the follow-up work.

That’s why I want to encourage Watir users who have changes they want to share to use this patching technique. They can share their changes with other users, get valuable feedback, and maybe find someone who will finish the work so that it can be accepted into the central Watir repository.

To illustrate, i describe a simple change that i recently made to Watir, explaining how this change could be distributed using this Ruby-specific technique, which I call an injection patch as well as the conventional kind of patch.

Recently a colleague and I wrote a test suite that would make sure that certain buttons would show up correctly. This is based on security rights, and there are a lot of pages and buttons and rights combinations, so it is tedious to test manually and a good thing to automate. We showed our tests to our manager. He knew about Watir’s flash method and asked us to flash each button as we tested it. This way you can really watch the verification happen. If a button doesn’t flash, that means it wasn’t tested. But they also slowed down our tests. Tests that took about 15 seconds to run, now took 60 seconds. This really bothered my colleague. How could I make both my manager and my co-worker happy?

My solution was to change the flash method. This is what the existing flash method looked like:


    def flash
      assert_exists
      10.times do
        highlight(:set)
        sleep 0.05
        highlight(:clear)
        sleep 0.05
      end
      nil
    end 

It’s flashing elements ten times each. With a one-tenth of a second delay (0.05 + 0.05) for each flash, that amounts to a second for every button we were testing. Ten flashes is fine when you are working IRB, which is what the flash command was originally written for, but it is excessive in this situation. So I changed the method to take an optional parameter indicating how many times to flash. Thus:


    def flash number=10
      assert_exists
      number.times do
        highlight(:set)
        sleep 0.05
        highlight(:clear)
        sleep 0.05
      end
      nil
    end

And then I the call in our tests to be button().flash(2) and only flash each button twice.

I mostly run Watir from a working copy that I keep closely synchronized with trunk. But my co-workers use a gem that I update every week or two. I didn’t want to go through the trouble of making them update to a new gem for this little change, so instead I added the following code to our test suite, after the existing require "watir" statement:


  class Watir::Element
    def flash number=10
      assert_exists
      number.times do
        highlight(:set)
        sleep 0.05
        highlight(:clear)
        sleep 0.05
      end
    nil
    end
  end

What this code does is replace the pre-existing Watir::Element#flash method that has already been loaded in memory (by require 'watir'), with the new method. The original Watir files are unchanged, but the loaded class has been modified.

This is the injection patch technique that I want people to use. People new to Ruby may not realize that you can extend or modify nearly any existing class using this technique. Because Watir is open-source any one can modify the original code, but because it is written in Ruby, any one can modify Watir’s classes without having to touch the original files. This is just one of the special things about Ruby.

I made up the “injection patch” name, because I don’t know what else to call it. I expect that some experienced Rubyist will think I’m overblowing this, because this really is just a consequence of the fact that in Ruby classes are never closed and remain open for redefined or added methods. (Actually there is a way in Ruby to freeze a class and keep this from happening. Or so I’ve read. But I’ve never heard of any one using it.)

A while back I called this technique a patch and this only seemed to annoy other Ruby programmers who seemed to have a fixed idea that a patch could only made up of conventionally formatted file diffs. But I don’t know what else to call it, so I’ll keep call it this until someone can suggest a better name.

In some languages, aspect-oriented tools allow programmers to inject code into existing classes. With Ruby it really is no more difficult than defining the code in the first place.

If you have changes to Watir that are restricted to a few methods, using this injection patch technique can often be the most effective way of distributing your changes. Users can just require your file to make use of your code, and in many cases your changes will work regardless the exact version of Watir they are using.

But if your changes are more widespread, especially if there are many small changes over different methods, then the conventional file-diff patch may be the better way to contribute them to the project and share them with others.

There are different tools you can use to create conventional patch files. I describe how to do it using TortoiseSVN.

First, you have to use Tortoise to check out a working copy of Watir. In Windows Explorer, go to the directory where you want to create your working copy, and right click and select SVN Checkout. This brings up a dialog. The URL of the repository is http://svn.openqa.org/svn/watir/trunk/watir/, and add a new checkout directory for your working copy. Click ‘OK’. This should take a moment to download and create your working copy. Make your changes to Watir. When you are done, select the working directory, right click, and choose TortoiseSVN > Create Patch. That’s all there is to it.

I did this for the change to the flash command that I explained above. This is what the patch file looks like:


Index: watir.rb
===============================================================
--- watir.rb(revision 1065)
+++ watir.rb(working copy)
@@ -2435,10 +2435,11 @@
       highlight(:clear)
     end

-    # causes the object to flash. Normally used in IRB when creating scripts
-    def flash
+    # Flash the element the specified number of times.
+    # Defaults to 10 flashes.
+    def flash number=10
       assert_exists
-      10.times do
+      number.times do
         highlight(:set)
         sleep 0.05
         highlight(:clear)
Posted by bret at 04:46 AM | Comments (1)