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. 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
  2. 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
  3. 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
  4. 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
  5. 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)
  6. 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)
  7. 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
  8. 18.

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

    elixir's Abstract Syntax Tree end
  9. 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]}
  10. 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
  11. 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]}, [], []}]}
  12. 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
  13. 27.
  14. 28.
  15. 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]}, [], []}]}
  16. 30.

    filtered by pattern match # we need function call {{:.,

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

    How to collect values • traverse AST • inject an

    AST to collect values • collect values at runtime
  19. 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 )
  20. 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)
  21. 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 )
  22. 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 )
  23. 40.

    summary • traverse AST • analyze AST and detect position

    of expression • inject AST to collect value and get result of expression
  24. 43.

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

    • Metaprogramming Elixir • pragprog.com/book/cmelixir/ metaprogramming-elixir • defmacro • leanpub.com/defmacro
  25. 44.

    ! Resources - Web • macro with elixir • www.slideshare.net/k1complete/elixir-

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

    END