12
Aug
11

Mustachio

It went like this:

Andrew: This service adds a moustache to any face! It’s awesome!

Me: I’m busy working.

A: What if we create an extension that would add a moustache to every face! It’d be awesome!

Me: I’m busy working.

A: It’d take only a few minutes anyway.

Me: Ok, whatever…

In the next 7 minutes or so I created a Chrome extension that puts a moustache on every face on every page. It took us another half an hour to test it, fix an occasional bug, draw a few icons, write a description for the Chrome gallery and pay $5 developer fee to Google since I never used my own account for extensions yet.

We tweeted about it and it went viral, to our surprise. We found out that the code doesn’t perform well on the Cedar stack of Heroku, so we had to redeploy to the old Bamboo – all while the usage levels were growing exponentially. Almost immediately it became obvious that our Face.com API key wouldn’t be enough to process all the requests and we asked to raise the limit (fortunately, they raised our limit promptly – thanks). In a matter of minutes we received an email from the American Mustache Institute, and in a matter of hours people began blogging about it (and not only in English). Our servers are still unable to process all the requests; we have to drop some of them and the tweets keep coming.

It was a good fun for a Friday afternoon pairing with the awesome @teabass!

02
Jun
11

A/B testing browser extensions

A in-depth description of how we split test InvisibleHand, our price comparison extension.

26
May
11

Detecting extension uninstallations on Chrome

Google Chrome, unlike Firefox, doesn’t allow to detect when the user uninstalls the extension, which is quite useful to understand user behaviour. There is a feature request on crbug.com with a discussion of this feature but it hasn’t been implemented yet.

At InvisibleHand we are really trying to understand user behaviour to improve our extension. In particular, we run A/B tests of our extension to track whether new features make the users to uninstall InvisibleHand less or more. To track the uninstallations in a fairly hacky way, we inject this piece of code as a content script into every single page:

var iFrameId = 'invisiblehand-uninstallation-notifier';
var silenceCount = 0;
var maxSilence = 3;

var timer = setInterval(function() {
  chrome.extension.sendRequest({topic: "heartbeat"}, function(reply) {
    if (reply && reply.alive)
      silenceCount = 0; // we want 3 consecutive silence events just to be sure
    else
      silenceCount++;
    if (silenceCount < maxSilence) return;
    clearInterval(timer);
    if (document.getElementById(iFrameId)) return;
    var iframe = document.createElement("iframe");
    iframe.id = iFrameId;
    iframe.src = "http://productsiframe.invisiblehand.co.uk/uninstall";
    iframe.style.display = "none";
    document.body.insertBefore(iframe, document.body.firstChild);
  })
}, 1000);

This code tries to connect to the background page every second and if it fails (when it fails, the callback is still called but the reply is undefined), we assume the extension was disabled or uninstalled, so we inject an invisible iframe into the current page to notify our server about the uninstall. It’s the server’s responsibility to count only one uninstall per user since there is a high probability that this code will be executed in every open tab. However, it’s not of utmost important to us since we’re tracking not the absolute number of uninstalls but a relative change.

However, there is a big problem with this piece of code apart from it being very hacky. Several users reported that it leaks memory and the tabs with this code running that are left open in background can leak hundreds of MBs of memory. We found it hard to reproduce it but it looks like the issue is only present on the version 11 and not present on version 12 of Google Chrome. Furthermore, some users have reported InvisibleHand crashing a lot when this code was deployed. Again, we were unable to reproduce it reliably but I tend to believe this setInterval() has something to do with memory leaks and crashes.

We usually deploy this code only for short periods of time (hours) to run a specific A/B test and roll it back (release a new version without this content script) as soon as possible. I’m really looking to the day when it’ll be possible to listen for the uninstallation event.

If you know why this code may be causing memory leaks and/or crashes, please let me know in the comments.

14
Dec
10

jQuery exception in Firefox 4

If jQuery is used as a part of a browser extension in Firefox, it will work for FF3 but not necessarily in FF4. In particular, when jQuery is loaded using loadSubScript() passing a window (a wrappedJSObject object) to it as a context, it will throw an exception in eventSupported():

“Component is not available” nsresult: “0×80040111 (NS_ERROR_NOT_AVAILABLE)”
FF4 throws this exception if the event that is being checked is not supported on these lines:

var isSupported = (eventName in el);

isSupported = typeof el[eventName] === “function”;
This problem seems to be specific to loading jQuery using loadSubScript() since I cannot reproduce it in the console.

A workaround is a simple try/catch block. I created a pull request for the jQuery team here: https://github.com/jquery/jquery/pull/128

17
Apr
10

Rails Mailer, GMail and Net::SMTPFatalError: 555 5.5.2 Syntax error.

As it turns out, you’ll get

Net::SMTPFatalError: 555 5.5.2 Syntax error.

if your from address looks like ‘Evgeny Shadchnev <email@gmail.com>”. Setting it to just ‘email@gmail.com’ helps.

09
Apr
10

FasterCSV and UTF-16

FasterCSV under Ruby 1.8 will throw an obscure error if the input is given in UTF-16:

FasterCSV::MalformedCSVError: Unquoted fields do not allow \r or \n (line 1).

The solution is to convert the data to UTF-8:


require 'iconv'
csv = Iconv.conv('utf-8', 'utf-16', csv)

Then, :encoding => 'u', flag for FasterCSV should be enough to process this file with no problems.

02
Apr
10

Ack in Project Textmate bundle fails with Ruby 1.9

Textmate is great but Find in Project is slow as molasses, so everybody’s using Ack in Project bundle. The problem is it doesn’t work with Ruby 1.9 (fails with to_plist ArgumentError) and the problem is Textmate, which provides plist.c that is incompatible with Ruby 1.9. The solution:

# Updating osx-plist for Ruby 1.9 compatibility
$> git clone git://github.com/kballard/osx-plist.git
$> cd osx-plist/ext/plist
$> ruby extconf.rb && make
$> cp plist.bundle /Applications/TextMate.app/Contents/SharedSupport/Support/lib/osx/

The credit for the finding the solution goes to James.

02
Apr
10

Focused unit tests in ruby 1.9

I was having a problem running focused unit tests with TextMate 1.5.9, ruby 1.9.1 and rails 2.3.5. TextMate passes the params to the script like this:

ruby unit/booking_test.rb --name=test_cost_is_calculated_correctly_when_theres_a_surcharge

This doesn’t work (all tests are run instead) because the parameter is not passed correctly to the script. This works, however:

ruby unit/booking_test.rb --name test_cost_is_calculated_correctly_when_theres_a_surcharge

On ruby 1.8.7 both ways of passing the argument work just fine, while on ruby 1.9.1 only the second way works.

Since we want to pass two arguments to the script (‘––name’, ‘test_foobar’) instead of one (‘––name=test_foobar’), let’s patch TextMate. Open
/Applications/TextMate.app/Contents/SharedSupport/Bundles/Ruby.tmbundle/Support/RubyMate/runscript.rb
and replace

if name and !name.empty?
args << "--name #{name}"
elsif test_name and !test_name.empty?
args << "--name test_#{test_name.gsub(/\s+/,'_')}"
elsif spec and !spec.empty? and context and !context.empty?
args << %Q{--name "/test_spec \\{.*#{context}\\} \\d{3} \\[#{spec}\\]/"}

with

args << "--name"
if name and !name.empty?
args << name
elsif test_name and !test_name.empty?
args << "test_#{test_name.gsub(/\s+/,'_')}"
elsif spec and !spec.empty? and context and !context.empty?
args << %Q{"/test_spec \\{.*#{context}\\} \\d{3} \\[#{spec}\\]/"}

This will make the focused unit tests work.

03
Jun
09

Javascript testing in Ruby with Selenium

InvisibleHand, a Firefox plugin that checks alternative prices as you are shopping online and alerts you if a bargain is found, consists of client part (the extension itself, in Javascript) and server part (the database of products, in Ruby). In order for the extension to always stay up-to-date it downloads pieces of JS code with price scraping logic from the server when it starts. I look after the JS code updating it whenever the layout of pages on retailers’ websites changes.

The challenge was to test this stuff automatically because the JS code makes part of a Ruby project and I want to ‘rake test’ it all. Getting a JS engine in my project would be an overkill, so I resorted to Selenium.

So, how to test a piece of Javascript code stored in your Ruby project?

First, download Selenium :)

Then, launch the server
java -jar selenium-server.jar

Then, install selenium client
sudo gem install selenium-client

In your unit test, launch Selenium:

attr_reader :browser

def setup
  @browser = Selenium::Client::Driver.new "localhost", 4444, "*firefox", "http://www.google.com/", 10000
  browser.start_new_browser_session
end

def teardown
  browser.close_current_browser_session
end

 

The base address that you use to launch Selenium (google in this example) doesn't matter as long as your code isn't doing cross-domain requests.

Then, in your test:

def test_js
  str = "Ah, Satan sees Natasha".to_json
  js = %Q[
    function reverseString(str) {
      var rstr = '';
      for (var i=str.length-1; i>=0; i--)
        rstr += str.charAt(i);
      return rstr;
    } 
    reverseString(#{str});
  ]
  result = browser.js_eval(js)
  assert_equal 'ahsataN sees nataS ,hA', result
end

A few comments:

  1. The conversion to json is not necessary in this example but if you are sending an array of data or strings with characters which should be escaped, use json.
  2. js_eval() function returns the result of the last line in the script.
  3. If you need to return anything more complex than a string or a number, consider converting your data to json before returning and then parsing it in ruby (that's what I'm doing in my tests but I omitted it here for clarity)

The problem with this code is that is damn slow because a new instance of Firefox is started for this test and it's not fast. However, it's better than no tests at all and if you don't need to start a new instance of firefox for every test, it's actually tolerable.
Loaded suite /Users/evgeny/invisiblehand/test/selenium/js_test
Started
.
Finished in 7.403807 seconds.

1 tests, 1 assertions, 0 failures, 0 errors

Good luck!

13
May
09

libxml Extra content at the end of the document

Ruby libxml parser that I use to process large xml files in SAX mode refused to process a file that looked perfectly valid, throwing ‘Extra content at the end of the document’ error somewhere in the middle of the file. It turned out that it disliked control character \x0B (vertical tab), which is not allowed in XML according to the spec.

To simply remove the vertical tabs from the file (or, rather, replace them with spaces), I tried using sed like this

sed s/\x0B/\ /g file.xml

but I found out that \xXX syntax is not supported by OSX sed version, which is a shame, so I used a ruby script, which, to my surprise, was quick enough to process a 800 MB file.

output = File.open("out.xml", 'w+')
File.open('file.xml').each{|p| output.puts p.gsub(/\x0B/, ' ')}




Follow

Get every new post delivered to your Inbox.