Thursday, July 15, 2010

Cucumber on RedBridge

With JRuby's RedBridge, Ruby applications get started from Java. For example Cucumber, does. Let's see how Cucumber runs in Java code.

Firstly, people use Cucumber like this:

jruby -S cucumber addition.feature

"cucumber" is installed in $JRUBY_HOME/bin directory and looks like a command. But "cucumber" is a Ruby script, so "cucumber" can be evaluated using JRuby Embed, RedBridge, API. How about "addition.feature"? Cucumber receives the feature name via ARGV constant. This means adding a file name to ARGV will work. From these analysis, I wrote CucumberRunner.

package evergreen;

import org.jruby.embed.LocalContextScope;
import org.jruby.embed.PathType;
import org.jruby.embed.ScriptingContainer;

public class CucumberRunner {
private String jrubyhome = "/Users/yoko/Projects/jruby";
private String cucumber = jrubyhome + "/bin/cucumber";
private String feature = jrubyhome + "/lib/ruby/gems/1.8/gems/cucumber-0.8.5/examples/i18n/en/features/addition.feature";

private CucumberRunner() {
ScriptingContainer container = new ScriptingContainer(LocalContextScope.SINGLETHREAD);
container.setHomeDirectory(jrubyhome);
container.runScriptlet("ARGV << '" + feature + "'");
container.runScriptlet(PathType.ABSOLUTE, cucumber);
}

public static void main(String[] args) {
new CucumberRunner();
}
}

This printed out:

# language: en
Feature: Addition
In order to avoid silly mistakes
As a math idiot
I want to be told the sum of two numbers

Scenario Outline: Add two numbers # /Users/yoko/Projects/jruby/lib/ruby/gems/1.8/gems/cucumber-0.8.5/examples/i18n/en/features/addition.feature:7
Given I have entered <input_1> into the calculator # cucumber-0.8.5/examples/i18n/en/features/step_definitons/calculator_steps.rb:14
And I have entered <input_2> into the calculator # cucumber-0.8.5/examples/i18n/en/features/step_definitons/calculator_steps.rb:14
When I press <button> # cucumber-0.8.5/examples/i18n/en/features/step_definitons/calculator_steps.rb:18
Then the result should be <output> on the screen # cucumber-0.8.5/examples/i18n/en/features/step_definitons/calculator_steps.rb:22

Examples:
| input_1 | input_2 | button | output |
| 20 | 30 | add | 50 |
| 2 | 5 | add | 7 |
| 0 | 40 | add | 40 |

3 scenarios (3 passed)
12 steps (12 passed)
0m0.179s
/Users/yoko/Projects/jruby/lib/ruby/gems/1.8/gems/cucumber-0.8.5/bin/cucumber:19:in `load': exit (SystemExit)
from /Users/yoko/Projects/jruby/bin/cucumber:19
Exception in thread "main" org.jruby.embed.EvalFailedException: exit
at org.jruby.embed.internal.EmbedEvalUnitImpl.run(EmbedEvalUnitImpl.java:127)
at org.jruby.embed.ScriptingContainer.runUnit(ScriptingContainer.java:1149)
at org.jruby.embed.ScriptingContainer.runScriptlet(ScriptingContainer.java:1194)
at evergreen.CucumberRunner.(CucumberRunner.java:16)
at evergreen.CucumberRunner.main(CucumberRunner.java:20)
Caused by: org.jruby.exceptions.RaiseException: exit
at (unknown).(unknown)(/Users/yoko/Projects/jruby/lib/ruby/gems/1.8/gems/cucumber-0.8.5/bin/cucumber:19)
at Kernel.load(/Users/yoko/Projects/jruby/bin/cucumber:19)
at (unknown).(unknown)(:1)

OK. Cucumber seems to execute SystemExit in the end. Java code isn't happy with this sort of exception since evaluation itself has been succeeded. So, I'm going to catch org.jruby.embed.EmbedEvalFailedException and error message raised in Ruby.

package evergreen;

import java.io.StringWriter;

import org.jruby.embed.EvalFailedException;
import org.jruby.embed.LocalContextScope;
import org.jruby.embed.PathType;
import org.jruby.embed.ScriptingContainer;

public class CucumberRunner1 {
private String jrubyhome = "/Users/yoko/Projects/jruby";
private String cucumber = jrubyhome + "/bin/cucumber";
private String feature = jrubyhome + "/lib/ruby/gems/1.8/gems/cucumber-0.8.5/examples/i18n/en/features/addition.feature";

private CucumberRunner1() {
StringWriter errorWriter = new StringWriter();
try {
ScriptingContainer container = new ScriptingContainer(LocalContextScope.SINGLETHREAD);
container.setError(errorWriter);
container.setHomeDirectory(jrubyhome);
container.runScriptlet("ARGV << '" + feature + "'");
container.runScriptlet(PathType.ABSOLUTE, cucumber);
} catch (EvalFailedException e) {
System.out.println("Cuke says: " + e.getMessage());
} finally {
System.out.println("Cuke also says: " + errorWriter.toString());
}
}

public static void main(String[] args) {
new CucumberRunner1();
}
}

All right, CuumberRunner1 printed out:

# language: en
Feature: Addition
In order to avoid silly mistakes
As a math idiot
I want to be told the sum of two numbers

Scenario Outline: Add two numbers # /Users/yoko/Projects/jruby/lib/ruby/gems/1.8/gems/cucumber-0.8.5/examples/i18n/en/features/addition.feature:7
Given I have entered <input_1> into the calculator # cucumber-0.8.5/examples/i18n/en/features/step_definitons/calculator_steps.rb:14
And I have entered <input_2> into the calculator # cucumber-0.8.5/examples/i18n/en/features/step_definitons/calculator_steps.rb:14
When I press <button> # cucumber-0.8.5/examples/i18n/en/features/step_definitons/calculator_steps.rb:18
Then the result should be <output> on the screen # cucumber-0.8.5/examples/i18n/en/features/step_definitons/calculator_steps.rb:22

Examples:
| input_1 | input_2 | button | output |
| 20 | 30 | add | 50 |
| 2 | 5 | add | 7 |
| 0 | 40 | add | 40 |

3 scenarios (3 passed)
12 steps (12 passed)
0m0.238s
Cuke says: exit
Cuke also says: /Users/yoko/Projects/jruby/lib/ruby/gems/1.8/gems/cucumber-0.8.5/bin/cucumber:19:in `load': exit (SystemExit)
from /Users/yoko/Projects/jruby/bin/cucumber:19

Now, everything is controllable from Java.

Then, what if Cucumber result is showed up in Swing window? Isn't it interesting? So, I set StringWriter to ScriptingContainer so that all standard output from Ruby will be caught in StringWriter. Then, I added small Swing code.

package evergreen;

import java.io.StringWriter;

import javax.swing.JFrame;
import javax.swing.JTextArea;

import org.jruby.embed.EvalFailedException;
import org.jruby.embed.LocalContextScope;
import org.jruby.embed.PathType;
import org.jruby.embed.ScriptingContainer;

public class CucumberRunner2 {
private String jrubyhome = "/Users/yoko/Projects/jruby";
private String cucumber = jrubyhome + "/bin/cucumber";
private String feature = jrubyhome + "/lib/ruby/gems/1.8/gems/cucumber-0.8.5/examples/i18n/en/features/addition.feature";

private CucumberRunner2() {
StringWriter writer = new StringWriter();
StringWriter errorWriter = new StringWriter();
try {
ScriptingContainer container = new ScriptingContainer(LocalContextScope.SINGLETHREAD);
container.setError(errorWriter);
container.setHomeDirectory(jrubyhome);
container.setWriter(writer);
container.runScriptlet("ARGV << '" + feature + "'");
container.runScriptlet(PathType.ABSOLUTE, cucumber);
} catch (EvalFailedException e) {
System.out.println("Cuke says: " + e.getMessage());
} finally {
System.out.println("Cuke also says: " + errorWriter.toString());
writeOnWindow(writer.toString());
}
}

private void writeOnWindow(String message) {
JFrame frame = new JFrame();
JTextArea text = new JTextArea(message);
frame.add(text);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}

public static void main(String[] args) {
new CucumberRunner2();
}
}

When I ran above code, a simple Swing window showed up with the result of Cucumber test.

Cucumber is Swing friendly with RedBridge. NetBeans plugin for Cucumber might be possible. Or, other Java based plugins for Cucumber might be easily implemented with RedBrdige.

No comments: