Using AppEngine (and GWT) in a CI environment | Kyle Baley

:

These days, I don’t much like being ahead of, behind, askance of, or otherwise deviated from the curve. It used to fit well with my “blogging as a nervous tic” phase of early 2008 when I would apparently post about every single compiler error I encountered and how it fit in with the grand scheme of things. But now, I prefer my technology much more Google-able. Does wonders for my productivity. In light of that, this post will fall into the category of “interesting to people searching for a solution to a specific problem.”

And so it was that I went and tried to set up a CI process for our brand spankin’ new AppEngine app. (Note to you fetishists: I didn’t mean that “spankin’” part literally. I don’t pretend to understand the euphemisms I use; I just use ‘em.)

It’s essentially the same process we’re using for our main BookedIN application, which also runs on AppEngine:

Compile and test configuration
In this configuration, we just compile the app and run the unit tests.

Run stable UI tests
This configuration performs the following steps:

  1. GWT compile the application. I.e. do all the magic that converts Java into JavaScript
  2. Launch GWT hosted mode
  3. Launch the UI tests

The first two steps are performed with Ant. The last is in Rake because the UI tests are done in Capybara. We launch hosted mode first because it’s orders of magnitude faster to run UI tests against a local hosted mode version than it is against a deployed version, especially because we clear out the datastore before each scenario.

Let me add some foreshadowing here. In order to launch hosted mode, we use the following Ant target:

   1: <target name="devmode" depends="" description="Run development mode">
   2:     <java fork="true" classname="com.google.gwt.dev.DevMode" 
   3:         dir="${basedir}/war" spawn="true">
   4:         <classpath>
   5:             <pathelement location="src" />
   6:             <path refid="project.class.path" />
   7:             <path refid="tools.class.path" />
   8:         </classpath>
   9:         <jvmarg value="-Xmx512M" />
  10:         <jvmarg value="-javaagent:${appengine.folder}/lib/agent/appengine-agent.jar" />
  11:         <jvmarg value="-Duser.dir=${basedir}/WAR" />
  12:         <arg line="-war" />
  13:         <arg value="${basedir}/war" />
  14:         <arg line="-logLevel" />
  15:         <arg value="INFO" />
  16:         <arg value="-server" />
  17:         <arg value="com.google.appengine.tools.development.gwt.AppEngineLauncher" />
  18:         <arg value="net.bookedin.bam.BAM" />
  19:     </java>    
  20: </target>

Note that this is running dev mode using the AppEngine agent. So it’s mimicking AppEngine behind the scenes…somehow.

The key to this target is where it says: spawn=”true”. This allows TeamCity to launch DevMode, then continue about its business. I.e. it doesn’t wait until DevMode is finished.

Run flaky UI tests
We have this one in here just because I got tired of seeing three or four failing tests every single time and 95% of the time, it was because of a PayPal sandbox issue. Having these “sometimes they work, sometimes they don’t” tests in a separate project means I can take the ones that fail in the previous configuration that much more seriously. All this configuration does is run UI tests that are tagged as @flaky.

Kill DevMode
We have this as a separate configuration because we want it to run always. If it’s set up as a separate step in one of the previous configurations, TeamCity won’t run it if any of the UI tests fail. As a separate configuration, it can be set to run after any build of “Run flaky UI tests” regardless of whether that build succeeded or failed.

The new app…

…is in AppEngine also but not in GWT (for reasons outlined elsewhere). Which I thought meant this would be easier. Alack, this is not true.

There is a corresponding “dev mode” in AppEngine. But it can’t be launched and then forgotten like it can in GWT. Here’s the target we originally had for it:

   1: <target name="runserver" description="Starts the development server.">
   2:     <java classname="com.google.appengine.tools.development.DevAppServerMain"
   3:           classpath="${appengine.tools.classpath}"
   4:           fork="true" failonerror="true">
   5:       <jvmarg value="-javaagent:${appengine.folder}/lib/agent/appengine-agent.jar"/>
   6:       <arg value="--address=localhost"/>
   7:       <arg value="--port=8080"/>
   8:       <arg value="war"/>
   9:     </java>
  10: </target>

Same idea as the previous one but with a different class name. And unfortunately, you can’t add spawn=”true” to this one. Something in DevAppServerMain doesn’t like spawn=”true” and complains. So if you run this target, it sits there and waits until you forcibly stop the server, either with Ctrl-C or something like the following (in another command window):

   1: <target name="stopserver">
   2:     <property environment="env"/>
   3:     <exec executable="${env.JAVA_HOME}/bin/jps" output="pid.out.file"/>
   4:     <loadfile srcfile="pid.out.file" property="pid.out">
   5:       <filterchain>
   6:         <linecontains>
   7:           <contains value="DevMode"/>
   8:         </linecontains>
   9:         <tokenfilter>
  10:             <deletecharacters chars="DevMode"/>
  11:             <trim/>
  12:             <ignoreblank/>
  13:         </tokenfilter>
  14:           <tailfilter lines="1" />
  15:       </filterchain>
  16:     </loadfile>
  17:  
  18:     <echo>Killing appengine java process with PID - "${pid.out}"</echo>
  19:  
  20:       <exec executable="taskkill">
  21:           <arg value="/f" />
  22:           <arg value="/fi"/>
  23:           <arg value='"PID eq ${pid.out}"'/>
  24:       </exec>
  25:  
  26:     <delete file="pid.out.file"/>
  27: </target>

Side note: Both of these were modified from a comment thread on an open issue with AppEngine, whereby the default “runserver” command leaves a stray java.exe process running after you kill it.

This method works pretty well for local development. But won’t for a CI configuration that’s relying on the “runserver” target to give up control so it can “run UI tests”.

“No matter,” says I, “surely someone else has discovered just how much money and time is saved through the practices of UI testing and CI and is doing the same on an AppEngine application.”

If you just chuckled to yourself at my apparent naïveté, then same on you! I remain convinced that such a person does exist. He or she just doesn’t like to brag is all…

A post to the AppEngine Google Group led to my eventual solution which came in the usual manner: it occurred to me during the process of typing out my question and/or one of my responses.

The basis for the solution is: the process works for GWT DevMode with an AppEngine agent; so run the app in GWT DevMode with an AppEngine agent.

Setting up a minimal GWT environment for the app was simple:

  1. Add gwt-dev.jar to my classpath (just for the purpose of testing; it’s not included when we deploy the app)
  2. Create a stub .gwt.xml file
  3. Use the same Ant target above to launch the app in DevMode

The stub .gwt.xml file is an almost empty XML file:

   1: <?xml version="1.0" encoding="UTF-8"?>
   2: <module rename-to="gunton">
   3: </module>

DevMode complains if this file is missing. The location is defined in the final argument of the <java> task in the Ant target.

That’s it. We can now launch our AppEngine app in a CI environment and it won’t wait for the user to forcibly close dev mode. We can use a similar “kill dev mode” target to stop it when the UI tests are done. Our one remaining issue is that “kill dev mode” target. At any given time, we may have up to two DevModes running on the CI server: one for the GWT app and one for the AppEngine app. When we go to kill them, we can’t distinguish one from the other…

…which leads to one final footnote. We’re using Windows. If you are using Linux on your CI server, much of this would be moot, as was suggested in the thread. From my understanding, you can more easily run a process in the background, in which case, you can go with the original “runserver” target. Which also makes it easier to figure out which process to kill when the UI tests have finished.

Kyle the PID off.