Archive for the ‘experiences’ Category
Run JRuby From Within A Jar And Package Your Own Gems Along
Jruby-in a jar already bundles rspec and rake, so the goal was to find out where it gets packaged.
Download the jruby source zip, extract it and open the build.xml file, search for “rspec” (there’s two occurences) and you’ll find that it’s passed in as an argument to the gem installer, add in another line with “cucumber”:
<target name="install-gems">
<property name="jruby.home" value="${basedir}"/>
<java classname="org.jruby.Main" fork="true" maxmemory="${jruby.launch.memory}" failonerror="true">
<classpath refid="build.classpath"/>
<classpath path="${jruby.classes.dir}"/>
<sysproperty key="jruby.home" value="${jruby.home}"/>
<arg value="--command"/>
<arg value="maybe_install_gems"/>
<arg value="rspec"/>
<arg value="rake"/>
<arg value="cucumber"/> <!-- add cucumber -->
<arg value="--env-shebang"/>
</java>
</target>
Then run ant:
$ ant jar-complete
To verify that everything is fine:
$ java -jar lib/jruby-complete.jar -S gem list *** LOCAL GEMS *** builder (2.1.2) cucumber (0.2.3) diff-lcs (1.1.2) polyglot (0.2.5) rake (0.8.4) rspec (1.2.2) sources (0.0.1) term-ansicolor (1.0.3) treetop (1.2.5)
Great we’ve now managed to package jruby-in-a-jar with some additional gems. Now to run cucumber on jruby in eclipse.
Eclipse and TDD
It’s been after quite a while that I’m back to doing eclipse plugins. In this span of time I learnt some good techniques — TDD. It’s a really good safety check to know that your code is in a working state.
Test-first-development or TDD as it is called is where you generally write tests first and then write code that makes these tests pass. This in some ways ensures that you write clean APIs that are simple to test, use and understand. The APIs don’t really do more that what is needed for the job. So far so good.
The fun began when I moved back to doing eclipse. I now realize that it is difficult to do TDD when I’m writing code that implements (or extends) some third party interfaces. These implementations return some more interfaces, which only compounds to the problem of test-first-development.
The way I write eclipse plugins is create a null implementation that does nothing at all. It merely prints method names of the methods being called on to the console. I then start off eclipse (in debug mode), understand the sequence in which the interface is invoked, and what eclipse does with the return values (that’s walking though a lot of code, BTW), I also sometimes need to look at similar implementation to understand what they do. I then gradually implement those methods in a way that what eclipse expects. This gives another meaning to BDD — breakpoint driven development.
This process is what most developers would probably do when using a framework for the first time. Most frameworks (unlike the eclipse frameworks) are much simpler than eclipse is. There are some known interfaces that are exposed. Since there are very few interfaces to implement, developers gradually get to know them very well. As the expectation out of these interfaces is known well, gradual use of these interfaces, makes it easy to write tests for the interface before implementing the interface itself, which means that developers can move from BDD to TDD.
This process is a quick (and IMO, dirty) way of writing code. The initial code is not written with testability in mind, writing unit tests at the end is quite difficult, and testability can be achieved after quite some bit of refactoring to allow make the code testable.
An example of this is a typical java webapp that consists of some servlets, and jsps containing tag-libs etc. There are very few known interfaces that the clients implement (the HttpServlet, Filter, FilterChain etc.) Since the webapp framework itself is very compact, a framework to unit test the implementations of interfaces exposed by the servlet spec has evolved over a period of time. Cactus is one such example:
Cactus is a simple test framework for unit testing server-side java code (Servlets, EJBs, Tag Libs, Filters, …) [...] It uses JUnit and extends it.
To test a servlet one would write a test as:
public class TestSampleServlet extends ServletTestCase {
public void testThatServletPrintsHelloWorld(){
Servlet servlet = new HelloWorldServlet();
...
session.setAttribute("name", "bob");
...
assertTrue("hello, bob", servlet.printHelloWorld());
}
}
It is mind boggling to imagine a framework similar to cactus to test the thousands of interfaces exposed by a framework like eclipse, and we are still not talking about the other, “smaller” projects under the eclipse umbrella.
Given this scenario, how do you do test-first-development ?
Source-opening of open-source ?
zx recently pointed out to this interesting, source-opening of a very popular eclipse based web development tools, Aptana.
Could this be a case of an over zealous new management person who’s at worst ignored his/her devs’ inputs.
So what does this mean to me as an eclipse developer ?
This change in the licence terms mean that in case I build plugins on top of Aptana, then I need to ask for a licence from Aptana because “The new license just gives us[Aptana] more visibility into who is redistributing the IDE. We[Aptana] want to keep a very high level of quality, therefore we[Aptana] want to be aware of any redistribution of the IDE, and we[Aptana] ask that if you are going to redistribute, you contact us for a license (free or otherwise).”
The license faq makes for an interesting read.
So what are the implications of this going forward?
It may mean that the next time I contribute something back to any open source project, I may need to look up as to what may become of my contribution. I contributed my work under EPL, but since the copyright belongs to the company maintaining the project, the company may just decide to change the license terms altogether.
I may also want to investigate who makes such decisions in the company. Is it an aliance of contributors from various companies (like OSGi); is it a foundation like Apache, or Eclipse, or Gentoo that has representatives from a lot of other companies which may put their foot down on such issues ?
Flash Player on 64-bit FireFox on Linux
Here’s how to get your firefox running on a 64 bit Linux machine to work with 32 bit plugins. I’ve tried this with gentoo, it should work with ubuntu or kubuntu or any other distro of your choice.
Download and install nspluginwrapper from http://gwenole.beauchesne.info/projects/nspluginwrapper/.
For ubuntu, there are some instructions on how to do this here (http://ubuntuforums.org/showthread.php?t=341727). On gentoo I had to
#emerge nspluginwrapper
Download the flash installer from the website here: http://fpdownload.macromedia.com/get/flashplayer/current/install_flash_player_9_linux.tar.gz. Untar the installer:
$ tar -zxvf install_flash_player_9_linux.tar.gz
Step into the installer directory:
cd install_flash_player_9_linux
Edit the installer file:
vi flashplayer-installer #use nano or gedit if you like
Navigate to line 47 (this inside the function exit_cpu) and comment out the line that says exit 1.
# the architecture is not supported
exit_cpu () {
echo ""
echo "ERROR: Your architecture, \'$1\', is not supported by the"
echo " $PRODUCT installer."
echo ""
#exit 1 # this was line 47
}
Install the flash plugin, I used the default paths, you can choose the paths according to what you have on your computer (I used /home/ketan/.mozilla)
./flashplayer-installer
Now verify that the plugin actually got installed in the directory that you specified
$ ls /home/ketan/.mozilla/plugins
Use nspluginwrapper to create a 64-bit wrapper around this 32 bit plugin. (thanks to Lance for a typo update)
$ nspluginwrapper -i /home/ketan/.mozilla/plugins/libflashplayer.so
Now restart firefox, that should get you to run firefox with flash player.
Composite Logger for Ant
When doing long builds in CruiseControl, ant build logs are generally logged using the XmlLogger.
In certain cases, say for example when the build is taking longer than usual, and needs to be killed/stopped, the XmlLogger does not flush contents to disk.
It would be nice if there is some sort of a composite logger than can chain any logger along with the XmlLogger.
I just happened to write one sometime today. This is the source code for a CompositeLogger that logs to a default logger, and the XmlLogger. This needs the environment variable ANT_LOG_PREFIX to be set (there’s no better way I can think of)
package com.thoughtworks.ant.logger;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
import org.apache.tools.ant.BuildEvent;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.BuildLogger;
import org.apache.tools.ant.DefaultLogger;
import org.apache.tools.ant.XmlLogger;
/**
* @author Ketan Padegaonkar
*/
public class CompositeAntLogger implements BuildLogger {
private ArrayList loggers;
private String logPrefix;
private PrintStream out;
public CompositeAntLogger() {
checkLogPrefix();
createLoggers();
}
public void setEmacsMode(boolean emacsMode) {
for (Iterator iterator = loggers.iterator(); iterator.hasNext();) {
BuildLogger logger = (BuildLogger) iterator.next();
logger.setEmacsMode(emacsMode);
}
}
public void buildFinished(BuildEvent event) {
for (Iterator iterator = loggers.iterator(); iterator.hasNext();) {
BuildLogger logger = (BuildLogger) iterator.next();
logger.buildFinished(event);
}
}
public void buildStarted(BuildEvent event) {
for (Iterator iterator = loggers.iterator(); iterator.hasNext();) {
BuildLogger logger = (BuildLogger) iterator.next();
logger.buildStarted(event);
}
}
public void messageLogged(BuildEvent event) {
for (Iterator iterator = loggers.iterator(); iterator.hasNext();) {
BuildLogger logger = (BuildLogger) iterator.next();
logger.messageLogged(event);
}
}
public void targetFinished(BuildEvent event) {
for (Iterator iterator = loggers.iterator(); iterator.hasNext();) {
BuildLogger logger = (BuildLogger) iterator.next();
logger.targetFinished(event);
}
}
public void targetStarted(BuildEvent event) {
for (Iterator iterator = loggers.iterator(); iterator.hasNext();) {
BuildLogger logger = (BuildLogger) iterator.next();
logger.targetStarted(event);
}
}
public void taskFinished(BuildEvent event) {
for (Iterator iterator = loggers.iterator(); iterator.hasNext();) {
BuildLogger logger = (BuildLogger) iterator.next();
logger.taskFinished(event);
}
}
public void taskStarted(BuildEvent event) {
for (Iterator iterator = loggers.iterator(); iterator.hasNext();) {
BuildLogger logger = (BuildLogger) iterator.next();
logger.taskStarted(event);
}
}
public void setMessageOutputLevel(int level) {
for (Iterator iterator = loggers.iterator(); iterator.hasNext();) {
BuildLogger logger = (BuildLogger) iterator.next();
logger.setMessageOutputLevel(level);
}
}
public void setOutputPrintStream(PrintStream output) {
// do nothing
}
public void setErrorPrintStream(PrintStream err) {
// do nothing
}
private void createLoggers() {
loggers = new ArrayList();
try {
loggers.add(createDefaultLogger());
loggers.add(createXmlLogger());
} catch (FileNotFoundException e) {
throw new BuildException("The loggers could not open the file", e);
}
}
private void checkLogPrefix() {
logPrefix = System.getenv("ANT_LOG_PREFIX");
if (logPrefix == null || logPrefix.trim().length() == 0)
throw new BuildException("You need to set the environment variable ANT_LOG_PREFIX.");
}
private DefaultLogger createDefaultLogger() throws FileNotFoundException {
DefaultLogger logger = new DefaultLogger();
out = new PrintStream(new FileOutputStream(logPrefix + ".txt"));
logger.setOutputPrintStream(out);
logger.setErrorPrintStream(out);
return logger;
}
private XmlLogger createXmlLogger() throws FileNotFoundException {
XmlLogger logger = new XmlLogger();
out = new PrintStream(new FileOutputStream(logPrefix + ".xml"));
logger.setOutputPrintStream(out);
logger.setErrorPrintStream(out);
return logger;
}
}