CakePHP: Autotest using Watchr

Posted on May 9th, 2010 in Web Development | 4 Comments »

I was taking a look at ZenTest a while ago, especially the autotest functionality. As stated in the ZenTest site, autotest:

  • Improves feedback by running tests continuously.
  • Continually runs tests based on files you’ve changed.
  • Get feedback as soon as you save. Keeps you in your editor allowing you to get stuff done faster.

To add more bang to your buck, you can get continuous notifications from growl every time you save a file, alerting you if any of your previous test cases have failed.

Googling around I wanted to see if this can be tied into testing with CakePHP. If I did a horrible job googling and there is already something out there that mimics this functionality please send me a link. Anyways, I stumbled across Watchr. Watchr is a tool that monitors a directory tree, and triggers a user defined action whenever an observed file is modified. At that point trigger a CakePHP test suite command line argument and send the results to growl. And there we have it, continuous testing with growl notifications on CakePHP using Watchr.

Step 1: Install the Watchr gem: http://github.com/mynyml/watchr

Step 2: Upload images that growl will use to display passed/failed cases

Step 3: Set up a watchr configuration file in app/config/watchr.rb

Step 4: Every time you develop have watchr runing. Type the following in your root directory:

watchr app/config/watchr.rb

End Result:

Pass:

Fail:

Failed and Passed images to pass to growl

Create a directory ~/.watchr_images and upload the following images or any image of your choice for failed and passed test cases:

Break down of the watchr.rb configuration file

Define a global variable that points to your cake console script. I wanted to load this variable from my aliases in .profile but couldn’t figure out how (my ruby skills suck):

$cake = "/Applications/MAMP/bin/php5/bin/php /Applications/MAMP/htdocs/yourProject/cake/console/cake.php"

Define a function that will parse out the details you want to pass to growl. I only cared about the number of passes and fails.

def growl(message)
  message = message.split('---------------------------------------------------------------')[3].split('Time taken by tests')[0]
  growlnotify = `which growlnotify`.chomp
  image = message.include?('fails') ? "~/.watchr_images/failed.png" : "~/.watchr_images/passed.png"
  options = "-w -n Watchr --image '#{File.expand_path(image)}' -m '#{message}'"
  system %(#{growlnotify} #{options} &)
end

Define a function that will run a command-line program and passes the result to growl

def run(cmd) 
  puts(cmd)
  result = `#{cmd}`
  growl result rescue nil
  puts result
end

Define a function that calls the test case of the pertaining model or controller that just got saved:

def test_changed_model_or_controller(file)
  type = file.split('/')[1]
  name = file.split('/')[2].split('.')[0]
  run "#{$cake} testsuite app case #{type}/#{name}" 
end

Define a function that calls the test case of the pertaining test case that just got saved:

def test_changed_test_case(file)
  type = file.split('/')[3]
  name = file.split('/')[4].split('.')[0]
  run "#{$cake} testsuite app case #{type}/#{name}"
end

Define a function that runs the entire test suite for changes in config files, app_controller and app_model:

def test_app()
  run "#{$cake} testsuite app all"
end

The heart of the script, the watchers. Here we state which files to monitor using regular expression pattern matching paths.

watch('app/(models|controllers)/(.*).php')  { |m| test_changed_model_or_controller(m[0]) }
watch('app/tests/cases/(models|controllers)/(.*).test.php')  { |m| test_changed_test_case(m[0]) }
watch('app/config/(.*).php') { |m| test_app() }
watch('app/(app_controller|app_model).php')  { |m| test_app() }

That’s it. Run the watcher via “watchr app/config/watchr.rb” as you develop and your test cases will continuously run giving you growl update notifications.

Download the watchr.rb config file


Sources:
Watchr Readme
Watchr – Most of my time was spent here modifying this script and applying it to a CakePHP scenario
Setting up Watchr and Rails
Watchr: A Flexible, Generic Alternative to AutoTest – This was the first article I stumbled across that got me started on Watchr
CakePHP – Running Tests in the Command Line

MySQL Gem on 64-bit Snow Leopard

Posted on November 28th, 2009 in Web Development | No Comments »

Finally took some time to move a small rails application from my old Macbook Pro to my Macbook Pro running Snow Leopard and MAMP. Setting up autotest which should have just been a few commands ended up being a battle getting the MySQL gem set up correctly.

Autotest setup

What should have been quick and simple…

sudo gem install ZenTest
gem install autotest-rails

Make a file named .autotest in the root directory of the application and add:

require "autotest/growl"

Ended up being…

1) Update the system first.:

sudo gem update --system

Error:

!!! The bundled mysql.rb driver has been removed from Rails 
2.2. Please install the mysql gem and try again: gem install 
mysql.

From this point the battle starts. I have recompiled the MySQL gem many times trying to get it working. Excerpts of a few errors:

Enclosing class/module 'mXML' for class XPointer not known
ERROR:  Error installing mysql:
ERROR: Failed to build gem native extension.
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby extconf.rb
checking for mysql_query() in -lmysqlclient... no
checking for main() in -lm... yes
checking for mysql_query() in -lmysqlclient... no
checking for main() in -lz... yes
checking for mysql_query() in -lmysqlclient... no
checking for main() in -lsocket... no
checking for mysql_query() in -lmysqlclient... no
checking for main() in -lnsl... no
checking for mysql_query() in -lmysqlclient... no
checking for main() in -lmygcc... no
checking for mysql_query() in -lmysqlclient... no
Gem files will remain installed in /Library/Ruby/Gems/1.8/gems/mysql-2.8.1 for inspection.
Results logged to /Library/Ruby/Gems/1.8/gems/mysql-2.8.1/ext/mysql_api/gem_make.out
 
Could not find main page README
Could not find main page README
Could not find main page README
Could not find main page README
ERROR:  Error installing rubynode:
ERROR: Failed to build gem native extension.
 
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby extconf.rb
==================== ERROR =====================
Please set RUBY_SOURCE_DIR to the source path of ruby 1.8.7 (2008-08-11)!
================================================
uninitialized constant MysqlCompat::MysqlRes

The solution…

Finally got it working following the steps in this article. One gotcha of this article – since we are installing on a 64 bit operating system the last step needs to be installed with the -arch x86_64 flag, not -arch i386.

First download the MAMP source here, then:

unzip MAMP_1.7.2_src.zip
cd MAMP_1.7.2_src
tar -xzvf mysql-5.0.41.tar.gz
cd mysql-5.0.41 
./configure --with-unix-socket-path=/Applications/MAMP/tmp/mysql/mysql.sock --without-server --prefix=/Applications/MAMP/Library
make -j2
cp libmysql/.libs/*.dylib /Applications/MAMP/Library/lib/mysql 
mkdir /Applications/MAMP/Library/include
cp -R include /Applications/MAMP/Library/include/mysql
sudo env ARCHFLAGS="-arch x86_64" gem install mysql -- --with-mysql-config=/Applications/MAMP/Library/bin/mysql_config