When I visited ErlangFactory/SF, John Hughes showed me how cool QuickCheck is, and it was obvious that it would be ideal for testing Erjang. Unfortunately QuviQ's excellent quickcheck for Erlang is closed source, so that posed a problem to really make use of it.
So ... I rolled my own Trifork QuickCheck hence forth known as triq. It's far from as fancy as QuviQ's, but it's free; and it allows me to do some amazing tests none the less. The code for it is in the Erjang repo under triq/src and it would be equally usable under either BEAM or Erjang of cause. It's undocumented but modelled after QuviQ's API.
If you're looking for an intro to QuickCheck look here.
Erjang/Beam Equivalence Tests
The really cool thing is that now distribution is off the ground in Erjang, we can write property tests and evaluate them both locally and remote (in a BEAM and an Erjang VM respectively), and validate that test results are equal. Very cool.
The property I'll use to show this is the equivalence of some random binary operators:
For any Erlang term A and B, and an operators OP in the set [
'>','<','==','=:=','/=','++'], the result of evaluating the expression "A OP B" in BEAM and Erjang should be equal.
We'll use the fact that, 1 < 2 can be expressed as apply(erlang,'<',[1,2]) for any operator. The function prop_binop describes this property:
prop_binop() ->
?FORALL({A,B,OP},
{any(),any(),
elements(['>', '<', '==', '=:=', '/=', '++'])},
begin
Here = here(erlang,OP,[A,B]),
There = there(erlang,OP,[A,B]),
Here == There
end).
The essence is very clear, I want to check that the binary operators yield the same results running on either BEAM VM or Erjang VM. The function here(Mod,Fun,Args) is essentialy the same as apply; and there(Mod,Fun,Args) does apply on a remote virtual machine using rpc:call. I also need to do a little fiddling around with exceptions to be able to check that exceptions are equivalent. Here is the complete implementation of there/3:
there(Mod,Fun,Args) when is_atom(Mod), is_atom(Fun), is_list(Args) ->
call(server(), Mod,Fun,Args).
call(Node,Mod,Fun,Args) ->
case rpc:call(Node, Mod,Fun,Args) of
{badrpc,{'EXIT',{Reason,[FirstTraceElement|_]}}} ->
{badrpc, {'EXIT',{Reason,[FirstTraceElement]}}};
Value -> Value
end.
The exception stuff is there because many operators will fail, such appending two atoms:
(erjang@renaissance.local)3> 'abc' ++ 'cde'.
** exception error: bad argument
in operator ++/2
called as abc ++ cde
When this happens inside rpc:call the resulting stack traces will be different and I'm really only interested in the top element of that stack trace.
Running the tests
To run a property test, you call triq:check(Property); i.e.,
(erjang@renaissance.local)1> triq:check(prop_binop()).
To run this for real, however, I need two nodes: one called erjang@renaissance.local and another one called beam@renaissance.local, first I launch a blank Erjang VM, which will serve as the "remote" server in this setup.
prompt$ ./erl.sh -name 'erjang@renaissance.local' -pa triq/ebin
Eshell V5.7.3 (abort with ^G)
(erjang@renaissance.local)1>
That server knows all it needs to know to run some code; I'll use the rpc module to talk to it. I make sure triq is in it's load path.
Next, the fun begins. I'll launch a BEAM VM, which serves as the "client" in this setup. I pass it the name of the server using the flag -other erjang.
prompt$ erl -other erjang -name beam -pa triq/ebin
Erlang R13B02 (erts-5.7.3) [source] [smp:2:2] [rq:2] [async-threads:0] [kernel-poll:false]
Eshell V5.7.3 (abort with ^G)
(beam@renaissance.local)1>
Let's run it, by calling triq:check(Mod), which will pick up any functions in Mod with a name that starts with prop_:
(beam@renaissance.local)1> triq:check(erjang_test).
..............Failed!
Failed after 15 tests
Simplified:
{ A , B , OP } = {['\x80'],'','++'}
false
Oops! it failed. After some digging around, I realized that this was in fact a bug in the distribution code. An atom '\x80' (a single-byte atom with the byte 0x80) when passed forth and back beween the BEAM and Erjang it turns into '\xc4'!. Looks like a character conversion happening there; and sure enough, the Java code:
byte[] data = new byte[] { (byte) 0x80 };
String s = new String(data);
System.out.println(Integer.toHexString(s.charAt(0) & 0xff));
Prints c4! Because the default charset on my Mac is not iso-latin-1. So, I'll have to hunt that one down.
Anyway, I thought it was pretty cool.
Recent Comments