Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Power Assert Inside in Elixir

Power Assert Inside in Elixir

Avatar for Takayuki Matsubara

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