$30 off During Our Annual Pro Sale. View Details »

Power Assert Inside in Elixir

Power Assert Inside in Elixir

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

Takayuki Matsubara

January 11, 2016
Tweet

More Decks by Takayuki Matsubara

Other Decks in Programming

Transcript

  1. 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. 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. 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. 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. • 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. • 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. 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. how to handle AST defmacro assert(ast) do # receive an

    elixir's Abstract Syntax Tree end
  9. 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. 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. 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. 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. 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]}, [], []}]}
  14. filtered by pattern match # we need function call {{:.,

    [], [{:__aliases__, [alias: false], [:Enum]}, :reverse]}, [], []} # |> Macro.to_string # "Enum.reverse()" # disregard [1, 2, 3]
  15. 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`
  16. How to collect values • traverse AST • inject an

    AST to collect values • collect values at runtime
  17. 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 )
  18. 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)
  19. 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 )
  20. 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 )
  21. summary • traverse AST • analyze AST and detect position

    of expression • inject AST to collect value and get result of expression
  22. ! Resources - Books • Programming Elixir • pragprog.com/book/elixir/programming- elixir

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

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