KEMBAR78
Cukeup nyc ian dees on elixir, erlang, and cucumberl | PDF
Cucumber and Elixir
Ian Dees • @undees
CukeUp! NYC 2013
Why Elixir?
The sad state of concurrency
int HandleMessage(MessageType type,
unsigned long arg1,
unsigned long arg2) {
switch (type)
{
case SOME_MESSAGE:
INFO* info = *((INFO*)arg2);
doSomethingWith(info.field);
return MEANINGFUL_ERROR_CODE;
// ...
}
// ...
}
Semaphore& s = resourceSemaphore();
s.acquire();
laboriouslyCopyDataFromResource();
s.release();
doSomethingWithData();
• Dangerous and hard-to-use message types
• Corruption- and deadlock-prone locks
It doesn’t have to be this way!
A taste of Elixir
http://elixir-lang.org
Ruby style, Erlang substance
Pattern matching
defmodule Ackermann do
def ack(0, n), do: n + 1
def ack(m, 0), do: ack(m - 1, 1)
def ack(m, n), do: ack(m - 1, ack(m, n - 1))
end
IO.puts Ackermann.ack(3, 9)
Actor-style concurrency
defmodule ShoutyEcho do
def echo_loop do
receive do
{sender, msg} ->
sender <- {:ok, String.upcase(msg)}
echo_loop
end
end
end
defmodule ShoutyEcho do
def echo_loop do
receive do
{sender, msg} ->
sender <- {:ok, String.upcase(msg)}
echo_loop
end
end
end
defmodule ShoutyEcho do
def echo_loop do
receive do
{sender, msg} ->
sender <- {:ok, String.upcase(msg)}
echo_loop
end
end
end
defmodule ShoutyEcho do
def echo_loop do
receive do
{sender, msg} ->
sender <- {:ok, String.upcase(msg)}
echo_loop
end
end
end
defmodule ShoutyEcho do
def echo_loop do
receive do
{sender, msg} ->
sender <- {:ok, String.upcase(msg)}
echo_loop
end
end
end
pid = spawn(ShoutyEcho, :echo_loop, [])
pid <- {self, "Your name here"}
receive do
{:ok, response} ->
IO.puts response
after 500 ->
IO.puts "Done"
end
pid = spawn(ShoutyEcho, :echo_loop, [])
pid <- {self, "Your name here"}
receive do
{:ok, response} ->
IO.puts response
after 500 ->
IO.puts "Done"
end
pid = spawn(ShoutyEcho, :echo_loop, [])
pid <- {self, "Your name here"}
receive do
{:ok, response} ->
IO.puts response
after 500 ->
IO.puts "Done"
end
pid = spawn(ShoutyEcho, :echo_loop, [])
pid <- {self, "Your name here"}
receive do
{:ok, response} ->
IO.puts response
after 500 ->
IO.puts "Done"
end
Cucumberl
https://github.com/membase/cucumberl
Feature: Gray Code
Scenario: Reset
Given the LEDs read "ooo"
-module(graycode).
-export([given/3, main/0]).
given([the, leds, read, _Input], State, _) ->
{ok, State}.
main() ->
cucumberl:run("./features/graycode.feature").
$ cucumberl
Feature: Gray Code
Scenario: Reset
Given the LEDs read "ooo"
1 scenarios
1 steps
Feature: Gray Code
Scenario: Reset
Given the LEDs read "ooo"
When I reset the counter
---------NO-STEP--------
a step definition snippet...
'when'([i,reset,the,counter], State, _) ->
undefined.
---------NO-STEP--------
a step definition snippet...
'when'([i,reset,the,counter], State, _) ->
undefined.
-module(graycode).
-export([given/3, 'when'/3, main/0]).
%% ...
'when'([i, reset, the, counter], State, _) ->
{ok, State}.
%% ...
$ cucumberl
Feature: Gray Code
Scenario: Reset
Given the LEDs read "ooo"
When I reset the counter
1 scenarios
2 steps
I know what you’re
thinking...
What happens with the
next “When” step?
Feature: Gray Code
Scenario: ...
...
When I reset the counter
Scenario: ...
...
When I press the button
Pattern matching!
'when'([i, reset, the, counter], State, _) ->
{ok, State};
'when'([i, press, the, button], State, _) ->
{ok, State}.
$ find . -type f
./ebin/graycode.beam
./features/graycode.feature
./src/graycode.erl
$ find . -type f
./ebin/graycode.beam
./features/graycode.feature
./src/graycode.erl
Just a .beam file!
And now in Elixir!
Feature: Gray Code
Scenario: Reset
Given the LEDs read "ooo"
-module(graycode).
-export([given/3, main/0]).
given([the, leds, read, _Input], State, _) ->
{ok, State}.
main() ->
cucumberl:run("./features/graycode.feature").
defmodule :graycode do
def given([:the, :leds, :read, input], _state, _) do
{:ok, input}
end
def main() do
:cucumberl.run("./features/graycode.feature")
end
end
$ iex
c("src/graycode.ex", "ebin")
$ find . -type f
./ebin/graycode.beam
./features/graycode.feature
./src/graycode.ex
$ cucumberl
Feature: Gray Code
Scenario: Reset
Given the LEDs read "ooo"
1 scenarios
1 steps
Feature: Gray Code
Scenario: Reset
Given the LEDs read "ooo"
When I reset the counter
---------NO-STEP--------
a step definition snippet...
'when'([i,reset,the,counter], State, _) ->
undefined.
---------NO-STEP--------
a step definition snippet...
'when'([i,reset,the,counter], State, _) ->
undefined.
def 'when'([:i, :reset, :the, :counter], _state, _) do
{:ok, '...'}
end
➡
== Compilation error on file src/graycode.ex ==
** (SyntaxError) ./src/graycode.ex:6: syntax error
before: ')'
How to translate?
Hacking Cucumberl
%% cucumberl_gen.erl
When = process_clauses('when',
lists:reverse(
sets:to_list(dict:fetch('when', Dict)))),
➡
When_ = process_clauses(when_,
lists:reverse(
sets:to_list(dict:fetch(when_, Dict)))),
%% cucumberl_gen.erl
When = process_clauses('when',
lists:reverse(
sets:to_list(dict:fetch('when', Dict)))),
➡
When_ = process_clauses(when_,
lists:reverse(
sets:to_list(dict:fetch(when_, Dict)))),
%% cucumberl_parser.erl
string_to_atoms(StrWords) ->
lists:map(fun (Y) -> list_to_atom(string:to_lower(Y)) end,
string:tokens(StrWords, " ")).
➡
list_to_valid_atom("when") ->
list_to_atom("when_");
list_to_valid_atom(Str) ->
list_to_atom(Str).
string_to_atoms(StrWords) ->
lists:map(fun (Y) -> list_to_valid_atom(string:to_lower(Y)) end,
string:tokens(StrWords, " ")).
%% cucumberl_parser.erl
string_to_atoms(StrWords) ->
lists:map(fun (Y) -> list_to_atom(string:to_lower(Y)) end,
string:tokens(StrWords, " ")).
➡
list_to_valid_atom("when") ->
list_to_atom("when_");
list_to_valid_atom(Str) ->
list_to_atom(Str).
string_to_atoms(StrWords) ->
lists:map(fun (Y) -> list_to_valid_atom(string:to_lower(Y)) end,
string:tokens(StrWords, " ")).
def when_([:i, :press, :the, :button], state, _) do
{:ok, _next(state)}
end
$ cucumberl
Feature: Gray Code
Scenario: Reset
Given the LEDs read "ooo"
When I reset the counter
1 scenarios
2 steps
def then([:the, :leds, :should, :read, expected],
state, _) do
{expected === state, state}
end
Scenario Outline: Counter
Given the LEDs read "<n>"
When I press the button
Then the LEDs should read "<nplus1>"
Examples:
| n | nplus1 |
| ... | ..o |
| ..o | .oo |
| .oo | .o. |
| .o. | oo. |
| oo. | ooo |
| ooo | o.o |
| o.o | o.. |
| o.. | ... |
def when_([:i, :press, :the, :button], state, _) do
{:ok, _next(state)}
end
defp _next(leds) do
case leds do
'...' -> '..o'
'..o' -> '.oo'
'.oo' -> '.o.'
'.o.' -> 'oo.'
'oo.' -> 'ooo'
'ooo' -> 'o.o'
'o.o' -> 'o..'
'o..' -> '...'
end
end
Caveats
1. Cucumberl has no idea
about the Elixir runtime
2.Who will do what
about When?
https://github.com/undees/cukeup

Cukeup nyc ian dees on elixir, erlang, and cucumberl