On Thu, Nov 24, 2016 at 8:23 PM Michael Jones <michael.jo...@gmail.com>
wrote:

>
>
> It would be a miracle if a behavioral tester found a difference here, as
> intel can attest. You can test every input or you can prove equality.
> Nothing else means equal.
>
>
>

Not in a modern world. Here is an Erlang program, which does the same as
your example:

-spec a(integer()) -> number() | undefined.
a(X) ->
    if
         X == 25638625386 -> exit({error, 42});
         X /= 0 -> 1 / X;
         true -> undefined
    end.

Random testing is unlikely to find this particular bug, but the concept of
concolic testing is more clever. In Erlang we have a tool, CuTer, which
performs concolic testing of programs. Running this on our example with
some seed value (the input 3) immediately finds the problem in seconds:

[jlouis@lady-of-pain tmp]$ cuter z a '[3]' --recompile
Compiling z.erl ... OK
Testing z:a/1 ...
.xxxxxxxx
z:a(25638625386)
xxxxxxxxxxxxxx
=== Inputs That Lead to Runtime Errors ===
#1 z:a(25638625386)

How does it work? In the above if-expression, we note that the seed value
of 3 took the branch behind 'X /= 0'. If we pick the branch 'X ==
25638625386' and then ask an SMT solver (Z3 is what Cuter uses by default)
if it can find something you can plug into X to make this go through.

In the more general case, we have traversed many such branch-decisions down
to a certain depth. We can then invert one of the branch decisions and ask
the SMT solver to find results which follows the path down to that branch,
and then goes in another direction, due to the inversion. The reason an SMT
solver is needed is because some of the earlier branch decisions might make
it non-trivial to find one that forces a new path deep down in the decision
tree. We get expressions of the form (X > 5) AND (X != 37) AND (Y < 7) AND
(Y + X == 25) and ask the solver for values X and Y such that the
expression is satisfied. Since we inverted one of the branch decisions, a
sat of this expression gives us a new path to explore. We can continue
forcing out new branches down to a certain depth.

Here is another function:

-spec b(pos_integer(), pos_integer(), pos_integer()) -> {ok, pos_integer()}.
b(X, Y, Z) ->
    case lists:sum([X,Y,Z]) of
        4321 -> exit(argh);
        Sum -> {ok, Sum}
    end.

which is given values X, Y and Z summing to 4321 is an error:

[jlouis@lady-of-pain tmp]$ cuter z b '[3,2,1]' --recompile
Compiling z.erl ... OK
Testing z:b/3 ...
.xxxxxx
z:b(1, 4281, 39)
xxxx
=== Inputs That Lead to Runtime Errors ===
#1 z:b(1, 4281, 39)

You are now in a territory where random testing is unlikely to find
anything. At least not naive random testing with uniform distributions. But
if you can ask the system (through profiling) what branch decisions were
taken, SMT solvers can produce inputs which forces us to take different
branches. In turn, they pick a specimen for each code block and makes sure
every possible code block is traversed.

Another solution is to use a profiler, see that X = 25638625386 is never
hit and then write a generator like this (Erlang QuickCheck)

my_int() ->
    frequency([
        {1, 25638625386},
        {1000, int()}]).

which will generate the nasty case 1/1001 times. Combined with suffix trees
over what probalistic paths the generator has hit, this also forces out
this value[0]

[0] Many computer games use the same trick. If a given ability were truly
random, some games would feel unfair. Hence, the distribution is
pseudo-random to make sure the given event happens at least with a certain
probability. See http://dota2.gamepedia.com/Pseudo-random_distribution for
instance, for Dota 2's variant.

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to