Wednesday, February 18, 2009

Hacking on FlexUnit Ant Task

I am working with FlexUnit Ant task by Peter Martin (great work, Peter!).
One thing was missing though - under Linux I found that the default gflashplayer executable didn't exist. Instead, we have a different flash player available as part of the project tree. But there was no way to override the default used by the FlexUnit's launcher class and changing PATH variable in Ant is just a bear.

So, to make a long story short, I added ability to specify flashPlayerCmdPattern argument to the flexunit ant task. In the process, I created a GenericLauncher class based on the MacSWFLauncher (which looks to be generic enough and the most up to date).

Works like a charm. One possible enhancement for someone who knows Ant better - ability to specify the flash player as inner task element (with the <env> and <argument> tags and whatnot). But this worked well enough for me.

Here's the patch that goes with FlexAntTasks-src.zip from 2008-02-18. Enjoy!



Index: src/com/adobe/ac/ant/tasks/FlexUnitLauncher.java
===================================================================
--- src/com/adobe/ac/ant/tasks/FlexUnitLauncher.java (revision 38987)
+++ src/com/adobe/ac/ant/tasks/FlexUnitLauncher.java (working copy)
@@ -8,24 +8,12 @@
private static final String WINDOWS_OS = "Windows";
private static final String MAC_OS_X = "Mac OS X";

- /**
- * Run the SWF that contains the FlexUnit tests.
- * @param swf the name of the SWF
- * @throws Exception if there is an error launching the tests.
- */
- public void runTests( String swf ) throws Exception
- {
- SWFLauncher launcher = createSWFLauncher();
-
- launcher.launch( swf );
- }
-
/**
* Creates a SWF launcher suitable for the OS.
*
* @return a new SWF launcher
*/
- private SWFLauncher createSWFLauncher()
+ public static SWFLauncher createSWFLauncher()
{
SWFLauncher launcher = null;

Index: src/com/adobe/ac/ant/tasks/FlexUnitTask.java
===================================================================
--- src/com/adobe/ac/ant/tasks/FlexUnitTask.java (revision 38987)
+++ src/com/adobe/ac/ant/tasks/FlexUnitTask.java (working copy)
@@ -61,7 +61,23 @@
private String failureProperty = "flexunit.failed";
private String swf;
private File reportDir;
+ private String flashPlayerCmdPattern = null;

+ public String getFlashPlayerCmdPattern() {
+ return flashPlayerCmdPattern;
+ }
+
+ /** Set flash player command line pattern. If set to a non-empty string,
+ * will use an OS-independent launcher based on this command line.
+ * Otherwise, will try to guess an OS-specific command line to use.
+ * Use MessageFormat syntax. Argument {0} is replaced with swf file.
+ *
+ * @param flashPlayerCmdPattern
+ */
+ public void setFlashPlayerCmdPattern(String flashPlayerCmdPattern) {
+ this.flashPlayerCmdPattern = flashPlayerCmdPattern;
+ }
+
/**
* Set the port to receive the test results on.
*
@@ -183,11 +199,17 @@

private void launchTestSuite()
{
- final FlexUnitLauncher browser = new FlexUnitLauncher();
+ SWFLauncher launcher;
+ if (flashPlayerCmdPattern != null && flashPlayerCmdPattern.trim().length() > 0)
+ {
+ launcher = new GenericLauncher(flashPlayerCmdPattern);
+ } else {
+ launcher = FlexUnitLauncher.createSWFLauncher();
+ }

try
{
- browser.runTests( swf );
+ launcher.launch( swf );
}
catch ( Exception e )
{
Index: src/com/adobe/ac/ant/tasks/GenericLauncher.java
===================================================================
--- src/com/adobe/ac/ant/tasks/GenericLauncher.java (revision 0)
+++ src/com/adobe/ac/ant/tasks/GenericLauncher.java (revision 0)
@@ -0,0 +1,90 @@
+package com.adobe.ac.ant.tasks;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.text.MessageFormat;
+
+/** Launch flash player generically.
+ * Takes a command line pattern, where argument {0} is
+ * replaced with the swf file name.
+ *
+ * @author dmitry
+ */
+public class GenericLauncher implements SWFLauncher
+{
+ private String commandLinePattern;
+
+
+ public GenericLauncher(String flashPlayerCmdPattern) {
+ this.commandLinePattern = flashPlayerCmdPattern;
+ }
+
+ public void launch( String swf ) throws Exception
+ {
+ String cmd = createCommandLine(swf);
+
+ Process process = null;
+
+ try
+ {
+ System.out.println( "Generic Launcher: " + cmd );
+ process = Runtime.getRuntime().exec( cmd );
+ }
+ catch ( Exception e )
+ {
+ throw e;
+ }
+
+ String error = readStream( process.getErrorStream() );
+
+ if ( error.length() > 0 )
+ {
+ throw new Exception( error );
+ }
+ }
+
+ private String createCommandLine(String swf) {
+ if (commandLinePattern == null) {
+ throw new IllegalArgumentException("CommandLinePattern must not be null");
+ }
+
+ MessageFormat f = new MessageFormat(commandLinePattern);
+ String cmd = f.format(new String[] { swf });
+ return cmd;
+ }
+
+/**
+ * Reads all the data available from an input stream and converts it into a
+ * String.
+ *
+ * @param input input stream to read
+ * @return converted string
+ * @throws Exception if an error occurs while reading from the stream
+ */
+ private String readStream( final InputStream input ) throws Exception
+ {
+ final ByteArrayOutputStream output = new ByteArrayOutputStream();
+
+ int i;
+
+ while ( ( i = input.read() ) != -1 )
+ {
+ output.write( i );
+ }
+
+ return new String( output.toByteArray() );
+ }
+
+ /** Set command line pattern. Use MessagePattern syntax.
+ * Argument {0} is replaced with the swf file name.
+ *
+ * @param commandLinePattern
+ */
+ public void setCommandLinePattern(String commandLinePattern) {
+ this.commandLinePattern = commandLinePattern;
+ }
+
+ public String getCommandLinePattern() {
+ return commandLinePattern;
+ }
+}