Packages

An extension on `Ecto.Schema` where you can provide additional options, which will be read by the corresponding `PropSchema.TestHarness` module, used in the test files to generate property tests.

Current section

16 Versions

Jump to

Compare versions

21 files changed
+292 additions
-259 deletions
  @@ -5,16 +5,6 @@
5 5 An extension of both `Ecto.Schema` and `ExUnitProperties` for auto-generation of changeset property tests.
6 6 Consists of two main modules: `PropSchema` and `PropSchema.TestHarness`
7 7
8 - ## Installation
9 -
10 - ```elixir
11 - def deps do
12 - [
13 - {:prop_schema, "~> 0.1.0"}
14 - ]
15 - end
16 - ```
17 -
18 8 ## Usage
19 9
20 10 #### Look at the documentation for full usage
  @@ -6,23 +6,29 @@
6 6 {<<"files">>,
7 7 [<<"lib">>,<<"lib/mix">>,<<"lib/mix/tasks">>,<<"lib/mix/tasks/print.ex">>,
8 8 <<"lib/prop_schema">>,<<"lib/prop_schema.ex">>,
9 - <<"lib/prop_schema/additional_properties.ex">>,
9 + <<"lib/prop_schema/customizers">>,
10 + <<"lib/prop_schema/customizers/additional_properties.ex">>,
11 + <<"lib/prop_schema/customizers/modifications.ex">>,
10 12 <<"lib/prop_schema/examples">>,<<"lib/prop_schema/examples/animagus">>,
11 13 <<"lib/prop_schema/examples/animagus/csv_mapping.ex">>,
12 14 <<"lib/prop_schema/examples/animagus/csv_mapping_properties.ex">>,
13 15 <<"lib/prop_schema/examples/animagus/results_of_print.exs">>,
14 - <<"lib/prop_schema/examples/example_additional_properties.ex">>,
15 - <<"lib/prop_schema/examples/example_module.ex">>,
16 - <<"lib/prop_schema/examples/example_owner.ex">>,
16 + <<"lib/prop_schema/examples/customizers">>,
17 + <<"lib/prop_schema/examples/customizers/example_additional_properties.ex">>,
18 + <<"lib/prop_schema/examples/customizers/example_modifications.ex">>,
17 19 <<"lib/prop_schema/examples/example_streamed.ex">>,
18 - <<"lib/prop_schema/executor.ex">>,<<"lib/prop_schema/generator">>,
20 + <<"lib/prop_schema/examples/schemas">>,
21 + <<"lib/prop_schema/examples/schemas/example_module.ex">>,
22 + <<"lib/prop_schema/examples/schemas/example_owner.ex">>,
23 + <<"lib/prop_schema/generator">>,
19 24 <<"lib/prop_schema/generator/base_properties.ex">>,
25 + <<"lib/prop_schema/generator/expression_modifier.ex">>,
20 26 <<"lib/prop_schema/generator/generator.ex">>,
21 27 <<"lib/prop_schema/stream.ex">>,<<"lib/prop_schema/test_harness.ex">>,
22 28 <<"mix.exs">>,<<"README.md">>]}.
23 29 {<<"licenses">>,[<<"MIT">>]}.
24 30 {<<"links">>,
25 - [{<<"Docs">>,<<"https://hexdocs.pm/prop_schema/0.4.1">>},
31 + [{<<"Docs">>,<<"https://hexdocs.pm/prop_schema/1.0.0">>},
26 32 {<<"GitHub">>,<<"https://github.com/podium/prop_schema">>}]}.
27 33 {<<"name">>,<<"prop_schema">>}.
28 34 {<<"requirements">>,
  @@ -36,4 +42,4 @@
36 42 {<<"optional">>,false},
37 43 {<<"repository">>,<<"hexpm">>},
38 44 {<<"requirement">>,<<"~> 0.4">>}]]}.
39 - {<<"version">>,<<"0.4.1">>}.
45 + {<<"version">>,<<"1.0.0">>}.
  @@ -1,9 +1,9 @@
1 1 defmodule Mix.Tasks.PropSchema.Print do
2 2 use Mix.Task
3 3 alias Mix.Tasks.Compile
4 - alias PropSchema.Executor
4 + alias PropSchema.TestHarness
5 5 require Logger
6 - require Executor
6 + require TestHarness
7 7
8 8 @moduledoc """
9 9 Prints the property tests generated for the given module. Use `--help` to see usage directions.
  @@ -65,6 +65,8 @@ defmodule Mix.Tasks.PropSchema.Print do
65 65 output_path Will output the results to a file at the given path. If file doesn't exist, it will be created
66 66 additional_props A module where the custom properties you have written are found. Make sure the compiler will
67 67 actually compile the module for the dev environemnt.
68 + filters A module where the custom filters you have written are found. Make sure the compiler will
69 + actually compile the module for the dev environemnt.
68 70 """
69 71
70 72 def run(args) do
  @@ -73,9 +75,10 @@ defmodule Mix.Tasks.PropSchema.Print do
73 75 Compile.run(args)
74 76 module = get_module(argv)
75 77 additional = get_additional(parsed)
78 + filters = get_filters(parsed)
76 79
77 80 module
78 - |> get_prop_tests(additional)
81 + |> get_prop_tests(additional, filters)
79 82 |> Enum.map(&Macro.to_string(&1))
80 83 |> Enum.join("\n\n")
81 84 |> output(module, parsed)
  @@ -84,7 +87,12 @@ defmodule Mix.Tasks.PropSchema.Print do
84 87 defp parse_args(args) do
85 88 {parsed, argv, errors} =
86 89 OptionParser.parse(args,
87 - strict: [help: :boolean, output_path: :string, additional_props: :string]
90 + strict: [
91 + help: :boolean,
92 + output_path: :string,
93 + additional_props: :string,
94 + filters: :string
95 + ]
88 96 )
89 97
90 98 Enum.each(errors, fn error -> Logger.warn("Erroneous option given: #{inspect(error)}") end)
  @@ -111,18 +119,30 @@ defmodule Mix.Tasks.PropSchema.Print do
111 119 end
112 120 end
113 121
114 - defp get_prop_tests(module, additional) do
122 + defp get_filters(parsed) do
123 + case Keyword.get(parsed, :filters) do
124 + nil ->
125 + nil
126 +
127 + additional ->
128 + String.to_existing_atom("Elixir." <> additional)
129 + end
130 + end
131 +
132 + defp get_prop_tests(module, additional, filters) do
115 133 List.flatten([
116 - Executor.__create_prop_test__(
134 + TestHarness.__create_prop_test__(
117 135 module,
118 136 :all_fields,
119 137 module.__prop_schema__(),
120 - additional
138 + additional,
139 + filters
121 140 ),
122 - Executor.__create_prop_test__(
141 + TestHarness.__create_prop_test__(
123 142 module,
124 143 module.__prop_schema__(),
125 - additional
144 + additional,
145 + filters
126 146 )
127 147 ])
128 148 end
  @@ -62,8 +62,6 @@ defmodule PropSchema do
62 62 Or for a more realistic example see example: [module](https://github.com/podium/prop_schema/blob/master/lib/prop_schema/examples/animagus/csv_mapping.ex), [properties](https://github.com/podium/prop_schema/blob/master/lib/prop_schema/examples/animagus/csv_mapping_properties.ex), and [generated tests](https://github.com/podium/prop_schema/blob/master/lib/prop_schema/examples/animagus/results_of_print.ex).
63 63 """
64 64
65 - alias PropSchema.Types
66 -
67 65 defmacro __using__(_opts) do
68 66 quote do
69 67 import PropSchema, only: [prop_schema: 2, prop_embedded: 1, prop_embedded_schema: 1]
  @@ -90,7 +88,7 @@ defmodule PropSchema do
90 88 field(:example_float, :float)
91 89 end
92 90 """
93 - @spec prop_schema(String.t(), do: Types.ast_expression()) :: Types.ast_expression()
91 + @spec prop_schema(String.t(), do: Macro.t()) :: Macro.t()
94 92 defmacro prop_schema(source, do: block) do
95 93 schema(source, block)
96 94 end
  @@ -99,7 +97,7 @@ defmodule PropSchema do
99 97 Deprecated in favor of `prop_embedded_schema/1`, see for usage details.
100 98 """
101 99 @deprecated "Use prop_embedded_schema/1 instead"
102 - @spec prop_embedded(do: Types.ast_expression()) :: Types.ast_expression()
100 + @spec prop_embedded(do: Macro.t()) :: Macro.t()
103 101 defmacro prop_embedded(do: block) do
104 102 embedded_schema(block)
105 103 end
  @@ -115,7 +113,7 @@ defmodule PropSchema do
115 113 field(:example_float, :float)
116 114 end
117 115 """
118 - @spec prop_embedded_schema(do: Types.ast_expression()) :: Types.ast_expression()
116 + @spec prop_embedded_schema(do: Macro.t()) :: Macro.t()
119 117 defmacro prop_embedded_schema(do: block) do
120 118 embedded_schema(block)
121 119 end
  @@ -153,7 +151,7 @@ defmodule PropSchema do
153 151 @doc """
154 152 Declares a field in the schema, processes it for use in `PropSchema.TestHarness` and then passes it through to `Ecto.Schema.field/3`. See `prop_schema/2` for examples.
155 153 """
156 - @spec prop_field(atom(), atom(), keyword()) :: Types.ast_expression()
154 + @spec prop_field(atom(), atom(), keyword()) :: Macro.t()
157 155 defmacro prop_field(name, type \\ :string, opts \\ []) do
158 156 quote do
159 157 PropSchema.__field__(__MODULE__, unquote(name), unquote(type), unquote(opts))
  @@ -189,7 +187,7 @@ defmodule PropSchema do
189 187 Declares a field in `__prop_schema__/2` which will either be the `name` with `"_id"` appended or the value of the `:foreign_key` option.
190 188 A UUID generator is provided for uid ids.
191 189 """
192 - @spec prop_belongs_to(atom(), module(), Keyword.t()) :: Types.ast_expression()
190 + @spec prop_belongs_to(atom(), module(), Keyword.t()) :: Macro.t()
193 191 defmacro prop_belongs_to(name, queryable, opts \\ []) do
194 192 ecto_opts =
195 193 Enum.reject(opts, fn {k, _v} -> not Enum.member?(@valid_belongs_to_options, k) end)
  @@ -221,7 +219,7 @@ defmodule PropSchema do
221 219 The only addition to the normal `Ecto.Schema.has_one/3` call is the `:additional_props` option which will tell the generator where
222 220 to find the additional props for building the associated struct.
223 221 """
224 - @spec prop_has_one(atom(), module(), Keyword.t()) :: Types.ast_expression()
222 + @spec prop_has_one(atom(), module(), Keyword.t()) :: Macro.t()
225 223 defmacro prop_has_one(name, queryable, opts \\ []) do
226 224 ecto_opts = Enum.reject(opts, fn {k, _v} -> not Enum.member?(@valid_has_options, k) end)
227 225
  @@ -242,7 +240,7 @@ defmodule PropSchema do
242 240 The only addition to the normal `Ecto.Schema.has_many/3` call is the `:additional_props` option which will tell the generator where
243 241 to find the additional props for building the associated structs.
244 242 """
245 - @spec prop_has_many(atom(), module(), Keyword.t()) :: Types.ast_expression()
243 + @spec prop_has_many(atom(), module(), Keyword.t()) :: Macro.t()
246 244 defmacro prop_has_many(name, queryable, opts \\ []) do
247 245 ecto_opts = Enum.reject(opts, fn {k, _v} -> not Enum.member?(@valid_has_options, k) end)
248 246
  @@ -275,7 +273,7 @@ defmodule PropSchema do
275 273 associated struct's `__prop_schema__/2` results will be modified so as to not generate it's associated many_to_many
276 274 structs due to the endless recursion that would cause.
277 275 """
278 - @spec prop_many_to_many(atom(), module(), Keyword.t()) :: Types.ast_expression()
276 + @spec prop_many_to_many(atom(), module(), Keyword.t()) :: Macro.t()
279 277 defmacro prop_many_to_many(name, queryable, opts \\ []) do
280 278 ecto_opts =
281 279 Enum.reject(opts, fn {k, _v} -> not Enum.member?(@valid_many_to_many_options, k) end)
  @@ -298,7 +296,7 @@ defmodule PropSchema do
298 296 The only addition to the normal `Ecto.Schema.embeds_one/3` call is the `:additional_props` option which will tell the generator where
299 297 to find the additional props for building the associated struct.
300 298 """
301 - @spec prop_embeds_one(atom(), module(), Keyword.t()) :: Types.ast_expression()
299 + @spec prop_embeds_one(atom(), module(), Keyword.t()) :: Macro.t()
302 300 defmacro prop_embeds_one(name, queryable, opts \\ []) do
303 301 ecto_opts =
304 302 Enum.reject(opts, fn {k, _v} -> not Enum.member?(@valid_embeds_one_options, k) end)
  @@ -322,7 +320,7 @@ defmodule PropSchema do
322 320 The only addition to the normal `Ecto.Schema.embeds_many/3` call is the `:additional_props` option which will tell the generator where
323 321 to find the additional props for building the associated structs.
324 322 """
325 - @spec prop_embeds_many(atom(), module(), Keyword.t()) :: Types.ast_expression()
323 + @spec prop_embeds_many(atom(), module(), Keyword.t()) :: Macro.t()
326 324 defmacro prop_embeds_many(name, queryable, opts \\ []) do
327 325 ecto_opts =
328 326 Enum.reject(opts, fn {k, _v} -> not Enum.member?(@valid_embeds_many_options, k) end)
  @@ -1,37 +0,0 @@
1 - defmodule PropSchema.AdditionalProperties do
2 - @moduledoc """
3 - A behaviour that is used to define additional properties not provided in the base properties module.
4 -
5 - ### How to Implement
6 -
7 - Once you have implemented the callbacks below (see `c:generate_prop/3` below), add the module that implements this behaviour as the option `additional_properties` in the `PropSchema.TestHarness.__using__/1` declaration.
8 - """
9 -
10 - @doc """
11 - Implement to define additional properties not provided in the `PropSchema.BaseProperties` module.
12 -
13 - ## Example
14 -
15 - def generate_prop(field, :float, %{positive: true, required: true}) do
16 - quote do
17 - {unquote(Atom.to_string(field)), float(min: 1)}
18 - end
19 - end
20 - """
21 - @callback generate_prop(atom(), atom(), map()) :: [{String.t(), StreamData.t()}]
22 - @optional_callbacks generate_prop: 3
23 -
24 - @doc """
25 - Implement to define miscellaneous properties.
26 -
27 - ## Example
28 -
29 - def generate_misc(:test_int) do
30 - quote do
31 - {"decoy_int", integer()}
32 - end
33 - end
34 - """
35 - @callback generate_misc(excluded :: atom()) :: [{String.t(), StreamData.t()}]
36 - @optional_callbacks generate_misc: 1
37 - end
Loading more files…