From 3560de521a405e375d4652cc84fdb8447564df4e Mon Sep 17 00:00:00 2001 From: Nikola Jichev Date: Thu, 31 May 2018 15:56:47 +0300 Subject: [PATCH 1/2] Create function generation. --- lib/stream_data_types.ex | 30 ++++++++++++++++++++++++++++++ test/stream_data/types_list.ex | 2 ++ test/stream_data/types_test.exs | 12 ++++++++++++ 3 files changed, 44 insertions(+) diff --git a/lib/stream_data_types.ex b/lib/stream_data_types.ex index 436af4b..97187d0 100644 --- a/lib/stream_data_types.ex +++ b/lib/stream_data_types.ex @@ -113,4 +113,34 @@ defmodule StreamDataTypes do defp generate({:type, _, :float, _}) do float() end + + defp generate({:type, _line, :fun, [{:type, _, :product, args}, ret_type]}) do + arity = length(args) + ret_type = generate(ret_type) + + f = create_fun(arity, ret_type) + + %StreamData{generator: f} + end + + defp create_fun(arity, %StreamData{generator: generator}) do + vars = Macro.generate_arguments(arity, Elixir) + + fn seed, size -> + ast = quote do + fn unquote_splicing(vars) -> + int = :erlang.phash2([unquote_splicing(vars)]) + new_seed = :rand.seed_s(:exsp, {int, 0, 0}) + var!(generator).(new_seed, var!(size)).root + end + end + + {root, _} = Code.eval_quoted(ast, [generator: generator, seed: seed, size: size]) + lazy_tree(root) + end + end + + defp lazy_tree(root, children \\ []) do + %StreamData.LazyTree{root: root, children: children} + end end diff --git a/test/stream_data/types_list.ex b/test/stream_data/types_list.ex index 83a5091..37835dd 100644 --- a/test/stream_data/types_list.ex +++ b/test/stream_data/types_list.ex @@ -7,4 +7,6 @@ defmodule StreamDataTest.TypesList do @type basic_neg_integer() :: neg_integer() @type basic_non_neg_integer() :: non_neg_integer() @type basic_pos_integer() :: pos_integer() + + @type literal_function_arity_1 :: (any() -> integer()) end diff --git a/test/stream_data/types_test.exs b/test/stream_data/types_test.exs index e5168d9..9392519 100644 --- a/test/stream_data/types_test.exs +++ b/test/stream_data/types_test.exs @@ -78,6 +78,18 @@ defmodule StreamData.TypesTest do end end + describe "literals" do + test "function literal with 1 argument" do + data = generate_data(:literal_function_arity_1) + + check all f <- data do + 1..10 + |> Enum.map(f) + |> Enum.each(&(assert is_integer(&1))) + end + end + end + defp generate_data(name, args \\ []) do Types.from_type(StreamDataTest.TypesList, name, args) end From f4755d166ef7d492d187c440f53d2bc6cc768b22 Mon Sep 17 00:00:00 2001 From: Nikola Jichev Date: Sun, 3 Jun 2018 23:11:31 +0300 Subject: [PATCH 2/2] Add random function arity. --- lib/stream_data_types.ex | 28 +++++++++++++++----- test/stream_data/types_list.ex | 3 +++ test/stream_data/types_test.exs | 46 ++++++++++++++++++++++++++++++--- 3 files changed, 66 insertions(+), 11 deletions(-) diff --git a/lib/stream_data_types.ex b/lib/stream_data_types.ex index 97187d0..6fdb702 100644 --- a/lib/stream_data_types.ex +++ b/lib/stream_data_types.ex @@ -114,19 +114,32 @@ defmodule StreamDataTypes do float() end + defp generate({:type, _line, :fun, [{:type, _, :any}, ret_type]}) do + ret_type = generate(ret_type) + + create_fun(ret_type) + end + defp generate({:type, _line, :fun, [{:type, _, :product, args}, ret_type]}) do arity = length(args) ret_type = generate(ret_type) - f = create_fun(arity, ret_type) - - %StreamData{generator: f} + create_fun(arity, ret_type) end - defp create_fun(arity, %StreamData{generator: generator}) do - vars = Macro.generate_arguments(arity, Elixir) + @fn_arity_cap 20 + defp create_fun(arity \\ nil, %StreamData{generator: generator}) do + f = fn seed, size -> + arity = + if arity do + arity + else + {int, _next_seed} = :rand.uniform_s(@fn_arity_cap, seed) + int + end + + vars = Macro.generate_arguments(arity, Elixir) - fn seed, size -> ast = quote do fn unquote_splicing(vars) -> int = :erlang.phash2([unquote_splicing(vars)]) @@ -135,9 +148,10 @@ defmodule StreamDataTypes do end end - {root, _} = Code.eval_quoted(ast, [generator: generator, seed: seed, size: size]) + {root, _} = Code.eval_quoted(ast, [generator: generator, size: size]) lazy_tree(root) end + %StreamData{generator: f} end defp lazy_tree(root, children \\ []) do diff --git a/test/stream_data/types_list.ex b/test/stream_data/types_list.ex index 37835dd..fa48c61 100644 --- a/test/stream_data/types_list.ex +++ b/test/stream_data/types_list.ex @@ -8,5 +8,8 @@ defmodule StreamDataTest.TypesList do @type basic_non_neg_integer() :: non_neg_integer() @type basic_pos_integer() :: pos_integer() + @type literal_function_arity_any :: (... -> float()) + @type literal_function_arity_0 :: (() -> neg_integer()) @type literal_function_arity_1 :: (any() -> integer()) + @type literal_function_arity_2 :: (integer(), atom() -> integer()) end diff --git a/test/stream_data/types_test.exs b/test/stream_data/types_test.exs index 9392519..8c7baaf 100644 --- a/test/stream_data/types_test.exs +++ b/test/stream_data/types_test.exs @@ -79,13 +79,51 @@ defmodule StreamData.TypesTest do end describe "literals" do + test "function with any arity" do + data = generate_data(:literal_function_arity_any) + + check all f <- data, i <- integer() do + assert is_function(f) + + {:arity, arity} = :erlang.fun_info(f, :arity) + args = List.duplicate(i, arity) + + ast = quote do + var!(f).(unquote_splicing(args)) + end + {int, _} = Code.eval_quoted(ast, f: f) + + assert is_float(int) + end + end + + test "function literal with 0 arguments" do + data = generate_data(:literal_function_arity_0) + + check all f <- data do + assert is_function(f, 0) + assert f.() == f.() # The function is pure + int = f.() + assert is_integer(int) + assert int < 0 + end + end + test "function literal with 1 argument" do data = generate_data(:literal_function_arity_1) - check all f <- data do - 1..10 - |> Enum.map(f) - |> Enum.each(&(assert is_integer(&1))) + check all f <- data, i <- integer() do + assert is_function(f, 1) + assert is_integer(f.(i)) + end + end + + test "function literal with 2 arguments" do + data = generate_data(:literal_function_arity_2) + + check all f <- data, a <- integer(), b <- integer() do + assert is_function(f, 2) + assert is_integer(f.(a, b)) end end end