Slide 1

Slide 1 text

Power Assert Inside in Elixir

Slide 2

Slide 2 text

self-introduction • Takayuki Matsubara • ma2gedev @github • ma2ge @twitter • Application Engineer @ M3, Inc. • Rails/Java/JavaScript application • Ruby: breadcrumble, chrono_logger, bundle-star • Elixir: power_assert_ex • HR7@MHX

Slide 3

Slide 3 text

precondition Understood • the basic syntax of Elixir • about Macro

Slide 4

Slide 4 text

github.com/ma2gedev ⭐

Slide 5

Slide 5 text

What is Power Assert?

Slide 6

Slide 6 text

Test code test "Enum.at should return the element at the given index" do array = [1, 2, 3, 4, 5, 6]; index = 2; two = 2 assert array |> Enum.at(index) == two end

Slide 7

Slide 7 text

in ExUnit 1) test Enum.at should return the elem... test/power_assert_sample_test.exs:5 Assertion with == failed code: array |> Enum.at(index) == two lhs: 3 rhs: 2 stacktrace: test/power_assert_sample_test.exs:7

Slide 8

Slide 8 text

in Power Assert 1) test Enum.at should return the elem... test/power_assert_sample_test.exs:5 array |> Enum.at(index) == two | | | | | 3 2 2 [1, 2, 3, 4, 5, 6] stacktrace: test/power_assert_sample_test.exs:7

Slide 9

Slide 9 text

failed results with descriptive message

Slide 10

Slide 10 text

• Power Assert (Groovy, Spock framework) • power-assert-js (JavaScript) • power_assert (Ruby) • power-assert-rs (Rust) • vital-power-assert (Vim script) • PowerAssert.Net (.Net) • power_assert.cr (Crystal) • PAssert (Swift) • PowerAssertEx (Elixir)

Slide 11

Slide 11 text

• Power Assert (Groovy, Spock framework) • power-assert-js (JavaScript) • power_assert (Ruby) • power-assert-rs (Rust) • vital-power-assert (Vim script) • PowerAssert.Net (.Net) • power_assert.cr (Crystal) • PAssert (Swift) • PowerAssertEx (Elixir)

Slide 12

Slide 12 text

PowerAssertEx Concept • Simple - provide only assert method • Reversible - can go back to ExUnit

Slide 13

Slide 13 text

PowerAssertEx |> expose

Slide 14

Slide 14 text

Point # test code(call `assert` method same as ExUnit) assert [1, 2, 3] |> Enum.reverse() |> Enum.sum() == 5 # output(need positions and values) [1, 2, 3] |> Enum.reverse() |> Enum.sum() == 5 | | [3, 2, 1] 6

Slide 15

Slide 15 text

tl;dr • assert • get positions and values • output

Slide 16

Slide 16 text

tl;dr • assert • get positions and values • output

Slide 17

Slide 17 text

review about Elixir's macro

Slide 18

Slide 18 text

how to handle AST defmacro assert(ast) do # receive an elixir's Abstract Syntax Tree end

Slide 19

Slide 19 text

AST in Elixir iex> quote do: sum(1, 2) {:sum, [], [1, 2]} iex> quote do: x {:x, [], Elixir} iex> quote do: Enum.sum(1, 2) {{:., [], [{:__aliases__, [alias: false], [:Enum]}, :sum]}, [], [1, 2]}

Slide 20

Slide 20 text

change AST into string of code iex> Macro.to_string(quote do: Enum.sum(1, 2)) "Enum.sum(1, 2)"

Slide 21

Slide 21 text

Positions and Values

Slide 22

Slide 22 text

Positions [1, 2, 3] |> Enum.reverse() |> Enum.sum() | | [3, 2, 1] 6

Slide 23

Slide 23 text

How to detect • traverse AST for collecting position each expression • detect position • convert each expression's AST to string of code • detect position with above string of code

Slide 24

Slide 24 text

target iex> quote do: [1, 2, 3] |> Enum.reverse() |> Enum.sum() {:|>, [context: Elixir, import: Kernel], [{:|>, [context: Elixir, import: Kernel], [[1, 2, 3], {{:., [], [{:__aliases__, [alias: false], [:Enum]}, :reverse]}, [], []}]}, {{:., [], [{:__aliases__, [alias: false], [:Enum]}, :sum]}, [], []}]}

Slide 25

Slide 25 text

traverse Macro.prewalk(ast, acc, fun) Macro.postwalk(ast, acc, fun) Macro.traverse(ast, acc, pre, post) # traverse/4 enabled after Elixir v1.2.0 `fun` is function and receives AST each expression

Slide 26

Slide 26 text

Enum.sum(1,2,3)'s AST

Slide 27

Slide 27 text

prewalk

Slide 28

Slide 28 text

postwalk

Slide 29

Slide 29 text

traverse with Macro.postwalk iex> Macro.postwalk(quote(do: [1, 2, 3] |> Enum.reverse() |> Enum.sum()), fn(ast) -> IO.inspect ast end) 1 2 3 [1, 2, 3] :Enum {:__aliases__, [alias: false], [:Enum]} :reverse {:., [], [{:__aliases__, [alias: false], [:Enum]}, :reverse]} {{:., [], [{:__aliases__, [alias: false], [:Enum]}, :reverse]}, [], []} {:|>, [context: Elixir, import: Kernel], [[1, 2, 3], {{:., [], [{:__aliases__, [alias: false], [:Enum]}, :reverse]}, [], []}]} :Enum {:__aliases__, [alias: false], [:Enum]} :sum {:., [], [{:__aliases__, [alias: false], [:Enum]}, :sum]} {{:., [], [{:__aliases__, [alias: false], [:Enum]}, :sum]}, [], []} {:|>, [context: Elixir, import: Kernel], [{:|>, [context: Elixir, import: Kernel], [[1, 2, 3], {{:., [], [{:__aliases__, [alias: false], [:Enum]}, :reverse]}, [], []}]}, {{:., [], [{:__aliases__, [alias: false], [:Enum]}, :sum]}, [], []}]}

Slide 30

Slide 30 text

filtered by pattern match # we need function call {{:., [], [{:__aliases__, [alias: false], [:Enum]}, :reverse]}, [], []} # |> Macro.to_string # "Enum.reverse()" # disregard [1, 2, 3]

Slide 31

Slide 31 text

convert AST to string of code and find position with regex code = {{:., [], [{:__aliases__, [alias: false], [:Enum]}, :reverse]}, [], []} |> Macro.to_string # "Enum.reverse()" # find position with `Regex.scan` by `code`

Slide 32

Slide 32 text

Values [1, 2, 3] |> Enum.reverse() |> Enum.sum() | | [3, 2, 1] 6

Slide 33

Slide 33 text

How to collect values • traverse AST • inject an AST to collect values • collect values at runtime

Slide 34

Slide 34 text

inject an AST Macro.postwalk(ast, acc, fun) • fun function can return modified AST

Slide 35

Slide 35 text

inject an AST to collect values # before Enum.reverse([1, 2, 3]) # after injected an AST ( v = Enum.reverse([1, 2, 3]) Agent.update(buffer, &[[pos, v] | &1]) v )

Slide 36

Slide 36 text

Agent!!! # after injected an AST {:ok, buffer} = Agent.start_link(fn -> [] end) ... ( v = Enum.reverse([1, 2, 3]) Agent.update(buffer, &[[pos, v] | &1]) v ) ... values = Agent.get(buffer, &(&1)) Agent.stop(buffer)

Slide 37

Slide 37 text

inject an AST when |> operator # before [1, 2, 3] |> Enum.reverse # after injected an AST [1, 2, 3] |> ( v = Enum.reverse # <= Does not work!!! Agent.update(buffer, &[[pos, v] | &1]) v )

Slide 38

Slide 38 text

modify an AST when |> operator # before [1, 2, 3] |> Enum.reverse # after injected an AST l_value = [1, 2, 3] ( v = Enum.reverse(l_value) # <= inject `l_value` # into first argument of rhs Agent.update(buffer, &[[pos, v] | &1]) v )

Slide 39

Slide 39 text

Positions and Values

Slide 40

Slide 40 text

summary • traverse AST • analyze AST and detect position of expression • inject AST to collect value and get result of expression

Slide 41

Slide 41 text

⌚ hereafter • please use power assert • welcome any feedback

Slide 42

Slide 42 text

! Resources - PowerAssertEx • PowerAssertEx • github.com/ma2gedev/powerassertex • hex.pm/packages/power_assert

Slide 43

Slide 43 text

! Resources - Books • Programming Elixir • pragprog.com/book/elixir/programming- elixir • Metaprogramming Elixir • pragprog.com/book/cmelixir/ metaprogramming-elixir • defmacro • leanpub.com/defmacro

Slide 44

Slide 44 text

! Resources - Web • macro with elixir • www.slideshare.net/k1complete/elixir- macroinaction1-14083087 • Understanding Elixir Macros, Part 1 - Basics • theerlangelist.com/article/macros_1

Slide 45

Slide 45 text

! Resources - Web • power-assert, mechanism and philosophy • www.slideshare.net/t_wada/power-assert- nodefest-2014 • Power Assert in Ruby • speakerdeck.com/k_tsj/power-assert-in- ruby

Slide 46

Slide 46 text

END