We want to drop the dependency of JRuby from our automated test suite, and move to native Ruby.
Ticket: tails#5731 (closed)
Why
We depend on JRuby only because of the sikuli-ruby
gem. Since
Debian's JRuby is too old this forces us to use RVM to install a more
recent JRuby and all required Ruby gems from RVM's non-Debian
sources, even though most of them are packaged for Debian. For all we
know, all of this can break in the future, especially after the Wheezy
release. Also, some parts of the RVM/gem mess have none or at best
scetchy authentication. Clearly we want all dependencies for the
automated test suite to be in Debian, and this mess is a big blocker
for that.
Also, in JRuby 1.7 the C extension was deprecated, which means that
two gems we depend on hsa become unusabe: ruby-libvirt
, which is
highly critical for us, and system_timer
, which is not so critical.
A move to native Ruby would put us in a much better position, since
all gems are packaged in Debian, and native Ruby
still has a C extension. To be able to do this we need a replacement
for the sikuli-ruby
gem, though.
Ways to replace the sikuli-ruby gem
Rjb
rjb seems maintained, if not actively developed. It is now in Debian Jessie and wheezy-backports.
Other projects using it or interesting stuff at:
- rjb-require
- rjb-examples
- poi_pond (uses Rjb)
- Antrap (uses rjb)
The jruby sikuli gem is quite simple, implementing it in Rjb can be as lightweight, or not.
See tails#6399 (closed), test/rjb-migration
branch.
Wrapping Sikuli commands via a stand-alone Sikuli server
If the Rjb solution doesn't work out, this fallback is a drop-in replacement for the Sikuli gem, and that we know will work no matter what, and that should be reasonably easy to implement.
In short, we create a pure Java program that controls a single
Screen
object, and listens for commands that that it translates into
Sikuli API calls for that Screen
object, and then responds
appropriately to the calling client.
Details
-
We write a pure Java program using the Java API for Sikuli, that takes the
$DISPLAY
, image dir etc. as arguments and then sets up all required Sikuli objects: Mouse and Keyboard robots, and a singleScreen
object. Let's call this the "Sikuli server". -
The Sikuli server listens (on a UNIX socket, or whatever) for "Sikuli commands" that it translates to Sikuli calls on the single
Screen
object via the Sikuli API, and then translates the result and sends that as a response. -
We completely re-write
sikuli_helper.rb
, defining aScreen
class (and possiblyRegion
; see below). Oninitialize()
it starts a Sikuli server instance (simply viaPopen()
or whatever). We implement simple wrappers for all methods we've used earlier (i.e.find()
,wait()
,type()
,hover()
andclick()
) that will send an appropriate command to the Sikuli server.
Example
Say screen
is an instance of sikuli_helper.rb
's Screen
. Then
screen.wait("image.png", 100)
will result in something like
wait,image.png,100
being sent to the Sikuli server, which it
translates into the call screenObject.wait("image.png", 100)
, and
responds back with something easily parsable, like
exception=ImageNotFound:Could not find image.png
(or whatever the
exact exception name and message are) on failure, otherwise it just
sends an ACK so screen.wait()
can stop blocking execution.
I'd rather not invent a new protocol entirely, and instead piggy-back e.g. on HTTP or something more appropriate. Also, I'd rather use a well-known, well-defined serialization format (YAML, JSON, you name it) instead of inventing a new one.
Obstacles
-
There may be some complexity to deal with
Region
objects (returned by e.g.find()
, which we use inwait_and_click()
) since we'd need some way (an identifier?) to know whichRegion
object insikuli_helper.rb
corresponds to whichRegion
(Java) object in the Sikuli server; we avoid this issue withScreen
since each Sikuli server deals with a singleScreen
object, but we can potentially deal with any number ofRegion
objects. A simple solution would be to only store the appropriate coordinates and dimensions insikuli_helper.rb
'sRegion
object and then whenever it's used we create a newRegion
object in the Sikuli server using those coordinates and dimensions. We could then implement methods via theScreen
object, likeRegion.click()
could use those coordinates and dimensions toScreen.click()
on the right place. We currently only useRegion
in ourwait_and_click
helper, so a ad-hoc solution would probably be enough. -
Getting Sikuli's key constants (e.g.
Sikuli::KEY_CTRL
) intosikuli_helper.rb
is not completely trivial. Perhaps we could make the Sikuli server send the values of all these so they can be stored into the appropriate constants uponScreen.initialize()
, when the server is first contacted? Otherwise we can always statically define them with magic values.
Roadmap
Complete the move to Rjb in test/rjb-migration
.