Power Assert Inside in Elixir

Power Assert Inside in Elixir

Slides at Elixir Meetup #1 in Drecom
http://beam-lang.connpass.com/event/23013/

509e5167fdb3871d6b6e045e34a3e019?s=128

Takayuki Matsubara

January 11, 2016
Tweet

Transcript

  1. Power Assert Inside in Elixir

  2. 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
  3. precondition Understood • the basic syntax of Elixir • about

    Macro
  4. github.com/ma2gedev ⭐

  5. What is Power Assert?

  6. 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
  7. 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
  8. 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
  9. failed results with descriptive message

  10. • 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)
  11. • 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)
  12. PowerAssertEx Concept • Simple - provide only assert method •

    Reversible - can go back to ExUnit
  13. PowerAssertEx |> expose

  14. 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
  15. tl;dr • assert • get positions and values • output

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

  17. review about Elixir's macro

  18. how to handle AST defmacro assert(ast) do # receive an

    elixir's Abstract Syntax Tree end
  19. 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]}
  20. change AST into string of code iex> Macro.to_string(quote do: Enum.sum(1,

    2)) "Enum.sum(1, 2)"
  21. Positions and Values

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

    [3, 2, 1] 6
  23. 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
  24. 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]}, [], []}]}
  25. 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
  26. Enum.sum(1,2,3)'s AST

  27. prewalk

  28. postwalk

  29. 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]}, [], []}]}
  30. filtered by pattern match # we need function call {{:.,

    [], [{:__aliases__, [alias: false], [:Enum]}, :reverse]}, [], []} # |> Macro.to_string # "Enum.reverse()" # disregard [1, 2, 3]
  31. 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`
  32. Values [1, 2, 3] |> Enum.reverse() |> Enum.sum() | |

    [3, 2, 1] 6
  33. How to collect values • traverse AST • inject an

    AST to collect values • collect values at runtime
  34. inject an AST Macro.postwalk(ast, acc, fun) • fun function can

    return modified AST
  35. 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 )
  36. 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)
  37. 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 )
  38. 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 )
  39. Positions and Values

  40. summary • traverse AST • analyze AST and detect position

    of expression • inject AST to collect value and get result of expression
  41. ⌚ hereafter • please use power assert • welcome any

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

  43. ! Resources - Books • Programming Elixir • pragprog.com/book/elixir/programming- elixir

    • Metaprogramming Elixir • pragprog.com/book/cmelixir/ metaprogramming-elixir • defmacro • leanpub.com/defmacro
  44. ! Resources - Web • macro with elixir • www.slideshare.net/k1complete/elixir-

    macroinaction1-14083087 • Understanding Elixir Macros, Part 1 - Basics • theerlangelist.com/article/macros_1
  45. ! 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
  46. END