diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 7e4450cb..ee232587 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: otp: ['23.3.4.7', '24.1'] - elixir: ['1.12.3'] + elixir: ['1.11.4', '1.12.3', '1.13.3'] steps: - uses: actions/checkout@v2 - uses: erlef/setup-beam@v1 diff --git a/.tool-versions b/.tool-versions index d09fd270..24efada7 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1,2 @@ -elixir 1.12.2-otp-23 +elixir 1.12.3 +erlang 24.1 diff --git a/lib/gradient/elixir_expr.ex b/lib/gradient/elixir_expr.ex index d532bf5a..a6a211b7 100644 --- a/lib/gradient/elixir_expr.ex +++ b/lib/gradient/elixir_expr.ex @@ -106,7 +106,7 @@ defmodule Gradient.ElixirExpr do "reraise " <> pp_expr(var) <> ", " <> pp_expr(var_stacktrace) end - def pp_expr({:call, _, {:remote, _, {:atom, _, :erlang}, {:atom, _, :error}}, [arg]}) do + def pp_expr({:call, _, {:remote, _, {:atom, _, :erlang}, {:atom, _, :error}}, [arg | _]}) do "raise " <> pp_raise_arg(arg) end @@ -405,7 +405,7 @@ defmodule Gradient.ElixirExpr do defp bin_pp_value(val), do: pp_expr(val) defp bin_set_value("", value), do: value - defp bin_set_value(sufix, value), do: value <> "::" <> sufix + defp bin_set_value(sufix, value), do: "(" <> value <> ")::" <> sufix defp bin_set_size("", :default), do: "" defp bin_set_size("", {:integer, _, size}), do: Integer.to_string(size) diff --git a/mix.exs b/mix.exs index e116ef3a..398b4d9d 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule Gradient.MixProject do [ app: :gradient, version: "0.1.0", - elixir: "~> 1.12", + elixir: "~> 1.11", elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, deps: deps(), diff --git a/test/examples/range.ex b/test/examples/1.12/range_step.ex similarity index 91% rename from test/examples/range.ex rename to test/examples/1.12/range_step.ex index f080893f..f9ff6db7 100644 --- a/test/examples/range.ex +++ b/test/examples/1.12/range_step.ex @@ -1,4 +1,4 @@ -defmodule RangeEx do +defmodule RangeStep do def range do 1..12 end diff --git a/test/examples/simple_range.ex b/test/examples/simple_range.ex new file mode 100644 index 00000000..41f4b614 --- /dev/null +++ b/test/examples/simple_range.ex @@ -0,0 +1,5 @@ +defmodule SimpleRange do + def range do + 1..12 + end +end diff --git a/test/examples/tuple.ex b/test/examples/tuple.ex index 95d7c367..bb102bf8 100644 --- a/test/examples/tuple.ex +++ b/test/examples/tuple.ex @@ -1,4 +1,4 @@ -defmodule Tuple do +defmodule TupleEx do def tuple do {:ok, 12} end diff --git a/test/gradient/ast_specifier_test.exs b/test/gradient/ast_specifier_test.exs index 5dcb3c6b..c85e14f3 100644 --- a/test/gradient/ast_specifier_test.exs +++ b/test/gradient/ast_specifier_test.exs @@ -101,7 +101,7 @@ defmodule Gradient.AstSpecifierTest do end test "tuple" do - {tokens, ast} = load("Elixir.Tuple.beam", "tuple.ex") + {tokens, ast} = load("Elixir.TupleEx.beam", "tuple.ex") [tuple_in_str2, tuple_in_str, tuple_in_list, _list_in_tuple, tuple | _] = AstSpecifier.run_mappers(ast, tokens) |> Enum.reverse() @@ -625,8 +625,29 @@ defmodule Gradient.AstSpecifierTest do ]} = guarded_case end + @tag :ex_lt_1_12 test "range" do - {tokens, ast} = load("Elixir.RangeEx.beam", "range.ex") + {tokens, ast} = load("Elixir.SimpleRange.beam", "simple_range.ex") + + [range | _] = AstSpecifier.run_mappers(ast, tokens) |> Enum.reverse() + + assert {:function, 2, :range, 0, + [ + {:clause, 2, [], [], + [ + {:map, 3, + [ + {:map_field_assoc, 3, {:atom, 3, :__struct__}, {:atom, 3, Range}}, + {:map_field_assoc, 3, {:atom, 3, :first}, {:integer, 3, 1}}, + {:map_field_assoc, 3, {:atom, 3, :last}, {:integer, 3, 12}} + ]} + ]} + ]} = range + end + + @tag :ex_gt_1_11 + test "step range" do + {tokens, ast} = load("Elixir.RangeStep.beam", "1.12/range_step.ex") [to_list, match_range, rev_range_step, range_step, range | _] = AstSpecifier.run_mappers(ast, tokens) |> Enum.reverse() @@ -711,6 +732,24 @@ defmodule Gradient.AstSpecifierTest do [block | _] = AstSpecifier.run_mappers(ast, tokens) |> Enum.reverse() + range = + if System.version() >= "1.12" do + {:map, 11, + [ + {:map_field_assoc, 11, {:atom, 11, :__struct__}, {:atom, 11, Range}}, + {:map_field_assoc, 11, {:atom, 11, :first}, {:integer, 11, 0}}, + {:map_field_assoc, 11, {:atom, 11, :last}, {:integer, 11, 5}}, + {:map_field_assoc, 11, {:atom, 11, :step}, {:integer, 11, 1}} + ]} + else + {:map, 11, + [ + {:map_field_assoc, 11, {:atom, 11, :__struct__}, {:atom, 11, Range}}, + {:map_field_assoc, 11, {:atom, 11, :first}, {:integer, 11, 0}}, + {:map_field_assoc, 11, {:atom, 11, :last}, {:integer, 11, 5}} + ]} + end + assert {:function, 10, :lc_complex, 0, [ {:clause, 10, [], [], @@ -719,13 +758,7 @@ defmodule Gradient.AstSpecifierTest do [ {:call, 11, {:remote, 11, {:atom, 11, Enum}, {:atom, 11, :reduce}}, [ - {:map, 11, - [ - {:map_field_assoc, 11, {:atom, 11, :__struct__}, {:atom, 11, Range}}, - {:map_field_assoc, 11, {:atom, 11, :first}, {:integer, 11, 0}}, - {:map_field_assoc, 11, {:atom, 11, :last}, {:integer, 11, 5}}, - {:map_field_assoc, 11, {:atom, 11, :step}, {:integer, 11, 1}} - ]}, + ^range, {nil, 11}, {:fun, 11, {:clauses, @@ -823,6 +856,7 @@ defmodule Gradient.AstSpecifierTest do {:bin_element, 7, {:string, 7, 'oops'}, :default, :default} ]} ]} + | _ ]} ]}, {:clause, [generated: true, location: 4], @@ -950,6 +984,7 @@ defmodule Gradient.AstSpecifierTest do :default, :default} ]} ]} + | _ ]} ], [], [], [ @@ -972,6 +1007,7 @@ defmodule Gradient.AstSpecifierTest do {:cons, 52, {:integer, 52, 49}, {:cons, 52, {:integer, 52, 50}, {nil, 52}}} ]} + | _ ]}, {:integer, 53, 1} ], [], [], [{:op, 55, :-, {:integer, 55, 1}}]} @@ -1049,6 +1085,8 @@ defmodule Gradient.AstSpecifierTest do [get2, get, update, empty, struct | _] = AstSpecifier.run_mappers(ast, tokens) |> Enum.reverse() + anno_line_17 = if(System.version() >= "1.12", do: [generated: true, location: 17], else: 17) + assert {:function, 8, :update, 0, [ {:clause, 8, [], [], @@ -1070,18 +1108,17 @@ defmodule Gradient.AstSpecifierTest do {:map, 17, [ {:map_field_exact, 17, {:atom, [generated: true, location: 17], :x}, - {:var, [generated: true, location: 17], :_@1}} + {:var, ^anno_line_17, :_@1}} ]} - ], [], [{:var, [generated: true, location: 17], :_@1}]}, - {:clause, [generated: true, location: 17], - [{:var, [generated: true, location: 17], :_@1}], + ], [], [{:var, ^anno_line_17, :_@1}]}, + {:clause, [generated: true, location: 17], [{:var, ^anno_line_17, :_@1}], [ [ {:call, [generated: true, location: 17], {:remote, [generated: true, location: 17], {:atom, [generated: true, location: 17], :erlang}, {:atom, [generated: true, location: 17], :is_map}}, - [{:var, [generated: true, location: 17], :_@1}]} + [{:var, ^anno_line_17, :_@1}]} ] ], [ @@ -1091,16 +1128,15 @@ defmodule Gradient.AstSpecifierTest do [ {:atom, 17, :badkey}, {:atom, 17, :x}, - {:var, [generated: true, location: 17], :_@1} + {:var, ^anno_line_17, :_@1} ]} ]} ]}, - {:clause, [generated: true, location: 17], - [{:var, [generated: true, location: 17], :_@1}], [], + {:clause, [generated: true, location: 17], [{:var, ^anno_line_17, :_@1}], [], [ {:call, [generated: true, location: 17], - {:remote, [generated: true, location: 17], - {:var, [generated: true, location: 17], :_@1}, {:atom, 17, :x}}, []} + {:remote, [generated: true, location: 17], {:var, ^anno_line_17, :_@1}, + {:atom, 17, :x}}, []} ]} ]}} ]} diff --git a/test/gradient/elixir_expr_test.exs b/test/gradient/elixir_expr_test.exs index 02b69e5d..31f12125 100644 --- a/test/gradient/elixir_expr_test.exs +++ b/test/gradient/elixir_expr_test.exs @@ -56,7 +56,7 @@ defmodule Gradient.ElixirExprTest do end |> ElixirExpr.pp_expr() - assert "pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>; for <>, do: {r, g, b}" == + assert "pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>; for <<(r)::8, (g)::8, (b)::8 <- pixels >>, do: {r, g, b}" == actual end @@ -67,7 +67,7 @@ defmodule Gradient.ElixirExprTest do end |> ElixirExpr.pp_expr() - assert "for <> >>, do: one" == actual + assert "for <> >>, do: one" == actual end test "receive" do @@ -328,8 +328,7 @@ defmodule Gradient.ElixirExprTest do end test "pp and format complex try expression" do - {_tokens, ast} = - Gradient.TestHelpers.load("Elixir.CallRemoteException.beam", "call_remote_exception.ex") + ast = Gradient.TestHelpers.load("Elixir.CallRemoteException.beam") {:function, _, :call, 2, [{:clause, _ann, _args, _guards, [try_expr]}]} = Enum.reverse(ast) |> List.first() @@ -338,7 +337,58 @@ defmodule Gradient.ElixirExprTest do # FIXME `raise {:badkey, :stack, _gen}` is not correct - expected = ~s""" + expected_v1_11 = ~s""" + try do + :ok + catch + :error, %Plug.Conn.WrapperError{} = e -> + exception = + Exception.normalize( + :error, + case e do + %{reason: _} -> _ + _ when :erlang.is_map(_) -> raise {:badkey, :reason, _} + _ -> _.reason() + end, + case e do + %{stack: _} -> _ + _ when :erlang.is_map(_) -> raise {:badkey, :stack, _} + _ -> _.stack() + end + ) + + _ = + Sentry.capture_exception(exception, [ + {:stacktrace, + case e do + %{stack: _} -> _ + _ when :erlang.is_map(_) -> raise {:badkey, :stack, _} + _ -> _.stack() + end}, + {:event_source, :plug} + ]) + + Plug.Conn.WrapperError.reraise(e) + + :error, e -> + _ = Sentry.capture_exception(e, [{:stacktrace, __STACKTRACE__}, {:event_source, :plug}]) + :erlang.raise(:error, e, __STACKTRACE__) + + kind, reason -> + message = + <<"Uncaught ", + case kind do + _gen when :erlang.is_binary(_gen) -> _gen + _gen -> String.Chars.to_string(_gen) + end::binary, " - ", Kernel.inspect(reason)::binary>> + + stack = __STACKTRACE__ + _ = Sentry.capture_message(message, [{:stacktrace, stack}, {:event_source, :plug}]) + :erlang.raise(kind, reason, stack) + end + """ + + expected_v1_12 = ~s""" try do :ok catch @@ -389,6 +439,8 @@ defmodule Gradient.ElixirExprTest do end """ + expected = if(System.version() >= "1.12", do: expected_v1_12, else: expected_v1_11) + assert expected == :erlang.iolist_to_binary(res) <> "\n" end end diff --git a/test/mix/tasks/gradient_test.exs b/test/mix/tasks/gradient_test.exs index 2d2d150b..b21546d5 100644 --- a/test/mix/tasks/gradient_test.exs +++ b/test/mix/tasks/gradient_test.exs @@ -103,7 +103,7 @@ defmodule Mix.Tasks.GradientTest do assert not String.contains?(output, ex_spec_error_msg) end - @tag if(System.version() >= "1.13", do: :skip, else: :ok) + @tag :ex_lt_1_13 test "--no-specify option" do output = run_task(test_opts([@s_wrong_ret_beam])) assert String.contains?(output, "on line 3") diff --git a/test/support/expr_data.ex b/test/support/expr_data.ex index 54f1964f..e45846c8 100644 --- a/test/support/expr_data.ex +++ b/test/support/expr_data.ex @@ -140,7 +140,7 @@ defmodule Gradient.ExprData do bin_with_bin_var(), bin_with_pp_int_size(), bin_with_pp_and_bitstring_size(), - {"bin float", elixir_to_ast(<<4.3::float>>), "<<4.3::float>>"} + {"bin float", elixir_to_ast(<<4.3::float>>), "<<(4.3)::float>>"} ] end @@ -150,7 +150,7 @@ defmodule Gradient.ExprData do <> = <<1, 2, 3, 4>> end - {"bin pattern matching with bin var", ast, "<> = <<1, 2, 3, 4>>"} + {"bin pattern matching with bin var", ast, "<<(a)::8, (_rest)::binary>> = <<1, 2, 3, 4>>"} end defp bin_joining_syntax do @@ -160,7 +160,7 @@ defmodule Gradient.ExprData do "a" <> x end - {"binary <> joining", ast, ~s(x = "b"; <<"a", x::binary>>)} + {"binary <> joining", ast, ~s(x = "b"; <<"a", (x\)::binary>>)} end defp bin_with_bin_var do @@ -170,7 +170,7 @@ defmodule Gradient.ExprData do <<"a", "b", x::binary>> end - {"binary with bin var", ast, ~s(x = "b"; <<"a", "b", x::binary>>)} + {"binary with bin var", ast, ~s(x = "b"; <<"a", "b", (x\)::binary>>)} end defp bin_with_pp_int_size do @@ -179,7 +179,7 @@ defmodule Gradient.ExprData do <> = <<"abcd">> end - {"binary with int size", ast, ~s(<> = "abcd")} + {"binary with int size", ast, ~s(<<(a\)::16>> = "abcd")} end defp bin_with_pp_and_bitstring_size do @@ -191,7 +191,7 @@ defmodule Gradient.ExprData do end expected = - "<> = <<1, 2, 3, 4, 5, 101, 114, 97, 115, 101, 32, 116, 104, 101, 32, 101, 118, 105, 100, 101, 110, 99, 101>>" + "<<(header)::8, (length)::32, (message)::bitstring-size(144)>> = <<1, 2, 3, 4, 5, 101, 114, 97, 115, 101, 32, 116, 104, 101, 32, 101, 118, 105, 100, 101, 110, 99, 101>>" {"binary with patter matching and bitstring-size", ast, expected} end diff --git a/test/test_helper.exs b/test/test_helper.exs index a86b330f..44a85b4f 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,12 +1,21 @@ -ExUnit.start() - defmodule ExamplesCompiler do @build_path "test/examples/_build/" + @version_step 0.01 + + @version (case System.version() do + v when v >= "1.13" -> 1.13 + v when v >= "1.12" -> 1.12 + v when v >= "1.11" -> 1.11 + end) + def compile(pattern) do case File.mkdir(@build_path) do :ok -> - paths = Path.wildcard(pattern) + paths = + Path.wildcard(pattern) + |> filter_too_new_files() + Kernel.ParallelCompiler.compile_to_path(paths, @build_path) :ok @@ -14,6 +23,39 @@ defmodule ExamplesCompiler do :error end end + + def excluded_version_tags do + case @version do + 1.11 -> + [:ex_gt_1_11, :ex_lt_1_11] + + 1.12 -> + [:ex_lt_1_11, :ex_lt_1_12, :ex_gt_1_12, :ex_gt_1_13] + + 1.13 -> + [:ex_lt_1_11, :ex_lt_1_12, :ex_lt_1_13, :ex_gt_1_13] + end + end + + defp filter_too_new_files(paths) do + case @version do + 1.13 -> + paths + + 1.12 -> + drop_versions(paths, ["1.13"]) + + 1.11 -> + drop_versions(paths, ["1.12", "1.13"]) + end + end + + defp drop_versions(paths, versions) do + Enum.filter(paths, &(not String.contains?(&1, versions))) + end end ExamplesCompiler.compile("test/examples/**/*.ex") +exlcude = ExamplesCompiler.excluded_version_tags() + +ExUnit.start(exclude: exlcude)